WanJetpack
💪 持续更新。WanJetpack使用Jetpack MVVM开发架构、单Activity多Fragment设计,项目结构清晰,代码简洁优雅,追求最官方的实现方式。欢迎star,非常感谢。已用到知识点:LiveData、ViewModel、DataBinding、ViewBinding、coroutines、Hilt、Paging3、Room、Navigation、TabLayout、BottomNavigationView、RecycleView、ViewPager2、Banner、Glide、Cookie、Retrofit2、启动页面、深色主题、沉浸式模式、Kotlin高阶函数。
Install / Use
/learn @lelelongwang/WanJetpackREADME
WanJetpack
项目简介
玩Android demo。用Jetpack MVVM开发架构、单Activity多Fragment项目设计,项目结构清晰,代码简洁优雅,追求最官方的实现方式。用到以下知识点: LiveData、ViewModel、DataBinding(包括双向绑定、BindingAdapter的使用)、ViewBinding、coroutines(包含flow、suspend、livedata协程构造器、flow协程构造器的使用)、Hilt、Paging3(包含RemoteMediator、加载状态)、Room、Navigation(通过ViewModel共享数据)、Banner(kotlin简单实现)、TabLayout、BottomNavigationView、RecycleView(包含ListAdapter、ConcatAdapter、PagingDataAdapter的使用)、ViewPager2、Glide、Cookie、Retrofit2、启动页面、深色主题、沉浸式模式、Kotlin高阶函数。
项目截图


项目参考demo
- Sunflower
- architecture-components-samples
- views-widgets-samples
- user-interface-samples
- architecture-samples
- compose-samples: 本demo中暂时不涉及Compose功能
项目知识点
LiveData
- LiveData文档
- LiveDataSample:
- 持有可被观察的类类似于EventBus或者RxJava。LiveData是一种可感知生命周期的组件
- LiveData与MutableLiveData区别
- LiveData使用
- 理解协程、LiveData 和 Flow
- Google 推荐在 MVVM 架构中使用 Kotlin Flow
- 关于Retrofit和LiveData相关参考demo:GithubBrowserSample[]
ViewModel
- ViewModel文档
- ViewModel 四种集成方式,即:
- ViewModel 中的 Saved State —— 后台进程重启时,ViewModel 的数据恢复;
- 在 NavGraph 中使用 ViewModel —— ViewModel 与导航 (Navigation) 组件库的集成;
- ViewModel 配合数据绑定 (data-binding) —— 通过使用 ViewModel 和 LiveData 简化数据绑定;
- viewModelScope —— Kotlin 协程与 ViewModel 的集成。
- 在Activity或者Fragment中如何处理ViewModel的三种方式(没太懂)
ViewBinding
DataBinding
- DataBinding文档
- 取代findviewbyId,类似于Butterknife。
coroutines
- 理解协程、LiveData 和 Flow
- liveData 协程构造方法提供了一个协程代码块,这个块就是 LiveData 的作用域,当 LiveData 被观察的时候,里面的操作就会被执行,当 LiveData 不再被使用时,里面的操作就会取消。 而且该协程构造方法产生的是一个不可变的LiveData,可以直接暴露给对应的视图使用。而 emit() 方法则用来更新 LiveData 的数据。
- 一个常见用例,比如当用户在 UI 中选中一些元素,然后将这些选中的内容显示出来。一个常见的做法是,把被选中的项目的 ID 保存在一个 MutableLiveData 里,然后运行 switchMap。现在在 switchMap 里,您也可以使用协程构造方法:
private val itemId = MutableLiveData<String>() val result = itemId.switchMap { liveData { emit(fetchItem(it)) } } - Google 推荐在 MVVM 架构中使用 Kotlin Flow
- 图解协程原理
Hilt
- hilt 和 Koin
Paging
- Paging 库 3.0.0正式版已发布,普天同庆!Paging 库可帮助您加载和显示来自本地存储或网络中更大的数据集中的数据页面。此方法可让您的应用更高效地利用网络带宽和系统资源。Paging 库的组件旨在契合推荐的 Android 应用架构,流畅集成其他 Jetpack 组件,并提供一流的 Kotlin 支持。
- 官方文档
- 官方demo:
- PagingSample : 本地数据库的demo
- PagingWithNetworkSample : 网络数据的demo
- Paging 库包含以下功能:
- 分页数据的内存中缓存。该功能可确保您的应用在处理分页数据时高效利用系统资源。
- 内置的请求重复信息删除功能,可确保您的应用高效利用网络带宽和系统资源。
- 可配置的 RecyclerView 适配器,会在用户滚动到已加载数据的末尾时自动请求数据。
- 对 Kotlin 协程和 Flow 以及 LiveData 和 RxJava 的一流支持。
- 内置对错误处理功能的支持,包括刷新和重试功能。
- Paging 组件及其在应用架构的集成:
- 定义数据源 : 数据源的定义取决于您从哪里加载数据。您仅需实现 PagingSource 或者 PagingSource 与 RemoteMediator 的组合:
- 如果您从单个源加载数据,例如网络、本地数据、文件、内存缓存等(不只是网络和数据库,其他如文件也可以使用Paging),实现 PagingSource 即可,如果您使用了 Room,从 2.3.0-alpha 开始,它将默认为您实现 PagingSource。
- 如果您从一个多层级数据源加载数据,就像带有本地数据库缓存的网络数据源那样。那么您需要实现 RemoteMediator 来合并两个数据源到一个本地数据库缓存的 PagingSource 中。
- PagingSource :
- PagingSource 可以定义一个分页数据的数据源,以及从该数据源获取数据的方式。
- LoadParams:PagingSource 的 密封类(sealed),包含有关要执行的加载操作的信息,其中包括要加载的键和要加载的项数。作为load()函数的参数使用
- LoadResult:PagingSource 的 密封类(sealed),包含加载操作的结果。LoadResult 是一个密封的类,根据 load() 调用是否成功。作为load()函数的返回值
- getRefreshKey(): 该方法接受 PagingState 对象作为参数,并且当数据在初始加载后刷新或失效时,该方法会返回要传递给 load() 方法的键。在后续刷新数据时,Paging 库会自动调用此方法。
- load(): 下图说明了load() 函数如何接收每次加载的键并为后续加载提供键:
- 代码示例:
// 自定义PagingSource类 private const val ARTICLE_STARTING_PAGE_INDEX = 0 class HomeArticlePagingSource( private val api: WanJetpackApi ) : PagingSource<Int, ApiArticle>() { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ApiArticle> { val page = params.key ?: ARTICLE_STARTING_PAGE_INDEX return try { val response = api.getHomeArticle(page) val datas = response.data.datas LoadResult.Page( data = datas, prevKey = if (page == ARTICLE_STARTING_PAGE_INDEX) null else page - 1, nextKey = if (page == response.data.pageCount) null else page + 1, ) } catch (exception: Exception) { LoadResult.Error(exception) } } override fun getRefreshKey(state: PagingState<Int, ApiArticle>): Int? { return null } }
- PagingData :
- 分页数据的容器被称为 PagingData,每次刷新数据时,都会创建一个 PagingData 的实例。如果要创建 PagingData 数据流,您需要创建一个 Pager 实例,并提供一个 PagingConfig 配置对象和一个可以告诉 Pager 如何获取您实现的 PagerSource 的实例的函数,以供 Pager 使用。
- Pager 类提供的方法可显示来自 PagingSource 的 PagingData 对象的响应式流。Paging 库支持使用多种流类型,包括 Flow、LiveData 以及 RxJava 中的 Flowable 和 Observable 类型。
- 通过 Pager().flow可以返回Flow<PagingData<ApiArticle>>。然后在ViewModel中.cachedIn(viewModelScope), cachedIn()运算符使数据流可共享,并使用提供的 CoroutineScope 缓存加载的数据
- 代码示例: (注:Pager 的 remoteMediator 参数可选项, RemoteMediator 是重点)
//Repository: fun getHomeArticle(): Flow<PagingData<ApiArticle>> { return Pager( config = PagingConfig(enablePlaceholders = false, pageSize = HOME_ARTICLE_PAGE_SIZE), pagingSourceFactory = { HomeArticlePagingSource(api) } ).flow }//ViewModel: fun getHomeArticle(): Flow<PagingData<ApiArticle>> { val newResult: Flow<PagingData<ApiArticle>> = repository.getHomeArticle().cachedIn(viewModelScope) currentArticleResult = newResult return newResult }
- PagingDataAdapter :
- 与定义 RecyclerView 列表 Adapter 时的通常做法相同:必须定义 onCreateViewHolder() 和 onBindViewHolder() 方法;指定 ViewHoler 和 DiffUtil.ItemCallback
- Adapter 及 UI ( Activity、Fragment )中的相关代码略。
- LoadType : 是个 enum 类,包含三种状态:REFRESH、PREPEND、APPEND。在 PagingSource 的 LoadParams 类中用到。
- 官方介绍:Type of load a [PagingData] can trigger a [PagingSource] to perform.
- REFRESH:[PagingData] content being refreshed, which can be a result of [PagingSource] invalidation, refresh that may contain content updates, or the initial load.
- PREPEND:Load at the start of a [PagingData].
- APPEND:Load at the end of a [PagingData].
- LoadState : 是个 sealed(密封) 类。
- 官方介绍:LoadState of a PagedList load - associated with a [LoadType].
- [LoadState] of any [LoadType] may be observed for UI purposes by registering a listener via [androidx.paging.PagingDataAdapter.addLoadStateListener] or [androidx.paging.AsyncPagingDataDiffer.addLoadStateListener]
- Paging 库通过 LoadState 对象公开可在界面中使用的加载状态。LoadState 根据当前的加载状态采用以下三种形式之一:
- 如果没有正在执行的加载操作且没有错误,则 LoadState 为 LoadState.NotLoading 对象。
- 如果有正在执行的加载操作,则 LoadState 为 LoadState.Loading 对象。
- 如果出现错误,则 LoadState 为 LoadState.Error 对象。
- 加载状态的三个场景:下拉刷新、上拉加载更多、首次进入页面中间的滚动条(及加载失败提醒)
- 显示加载状态 : 可通过两种方法在界面中使用 LoadState:使用监听器,以及使用特殊的列表适配器在 RecyclerView 列表中直接显示加载状态。
- 方法一、 使用监听器获取加载状态: 为了获取加载状态以用于界面中的一般用途,PagingDataAdapter 中提供了 addLoadStateListener()、loadStateFlow 两种方式。来自 loadStateFlow 或 addLoadStateListener() 的更新可确保与界面的更新保持同步。这意味着,如果您收到 NotLoading.Incomplete 的 LoadState,则可以确定加载已完成,并且界面也已相应更新。
// addLoadStateListener 方式。 articleAdapter.addLoadStateListener { when (it.refresh) { is LoadState.NotLoading -> { progressBar.visibility = View.INVISIBLE recyclerView.visibility = View.VISIBLE } is LoadState.Loading -> { progressBar.visibility = View.VISIBLE recyclerView.visibility = View.INVISIBLE } is LoadState.Error -> { val state = it.refresh as LoadState.Error progressBar.visibility = View.INVISIBLE Toast.makeText(this, "Load Error: ${state.error.message}", Toast.LENGTH_SHORT).show() } } }// loadStateFlow 方式 // collectLatest 是个 suspend 函数,所以要在协程或者另一个 suspend
- 方法一、 使用监听器获取加载状态: 为了获取加载状态以用于界面中的一般用途,PagingDataAdapter 中提供了 addLoadStateListener()、loadStateFlow 两种方式。来自 loadStateFlow 或 addLoadStateListener() 的更新可确保与界面的更新保持同步。这意味着,如果您收到 NotLoading.Incomplete 的 LoadState,则可以确定加载已完成,并且界面也已相应更新。
Related Skills
node-connect
337.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
337.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.2kCommit, push, and open a PR
