AAC是很是不错的一套框架组件,若是你还未进行了解,推荐你阅读我以前的系列文章:java
Android Architecture Components Part1:Roomandroid
Android Architecture Components Part2:LiveDatagit
Android Architecture Components Part3:Lifecyclegithub
Android Architecture Components Part4:ViewModel数据库
通过一年的发展,AAC又推出了一系列新的组件,帮助开发者更快的进行项目框架的构建与开发。此次主要涉及的是对Paging运用的全面介绍,相信你阅读了这篇文章以后将对Paging的运用了如指掌。api
Paging专一于有大量数据请求的列表处理,让开发者无需关心数据的分页逻辑,将数据的获取逻辑彻底与ui隔离,下降项目的耦合。网络
但Paging的惟一局限性是,它须要与RecyclerView结合使用,同时也要使用专有的PagedListAdapter。这是由于,它会将数据统一封装成一个PagedList对象,而adapter持有该对象,一切数据的更新与变更都是经过PagedList来触发。app
这样的好处是,咱们能够结合LiveData或者RxJava来对PagedList对象的建立进行观察,一旦PagedList已经建立,只需将其传入给adapter便可,剩下的数据操更新操做将由adapter自动完成。相比于正常的RecyclerView开发,简单了许多。框架
下面咱们经过两个具体实例来对Paging进行了解dom
Paging在Database中的使用很是简单,它与Room结合将操做简单到了极致,我这里将其概括于三步。
首先第一步咱们须要使用DataSource.Factory抽象类来获取Room中的数据,它内部只要一个create抽象方法,这里咱们无需实现,Room会自动帮咱们建立PositionalDataSource实例,它将会实现create方法。因此咱们要作的事情很是简单,以下:
@Dao interface ArticleDao { // PositionalDataSource @Query("SELECT * FROM article") fun getAll(): DataSource.Factory<Int, ArticleModel> }
咱们只需拿到实现DataSource.Factory抽象的实例便可。
第一步就这么简单,接下来看第二步
如今咱们在ViewMode中调用上面的getAll方法获取全部的文章信息,而且将返回的数据封装成一个LiveData,具体以下:
class PagingViewModel(app: Application) : AndroidViewModel(app) { private val dao: ArticleDao by lazy { AppDatabase.getInstance(app).articleDao() } val articleList = dao.getAll() .toLiveData(Config( pageSize = 5 )) }
经过DataSource.Factory的toLiveData扩展方法来构建PagedList的LiveData数据。其中Config中的参数表明每页请求的数据个数。
咱们已经拿到了LiveData数据,接下来进入第三步
前面已经说了,咱们要实现PagedListAdapter,并将第二步拿到的数据传入给它。
PagedListAdapter与RecyclerView.Adapter的使用区别不大,只是对getItemCount与getItem进行了重写,由于它使用到了DiffUtil,避免对数据的无用更新。
class PagingAdapter : PagedListAdapter<ArticleModel, PagingVH>(diffCallbacks) { companion object { private val diffCallbacks = object : DiffUtil.ItemCallback<ArticleModel>() { override fun areItemsTheSame(oldItem: ArticleModel, newItem: ArticleModel): Boolean = oldItem.id == newItem.id override fun areContentsTheSame(oldItem: ArticleModel, newItem: ArticleModel): Boolean = oldItem == newItem } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagingVH = PagingVH(R.layout.item_paging_article_layout, parent) override fun onBindViewHolder(holder: PagingVH, position: Int) = holder.bind(getItem(position)) }
这样adapter也已经构建完成,最后一旦PagedList被观察到,使用submitList传入到adapter便可。
viewModel.articleList.observe(this, Observer { adapter.submitList(it) })
一个基于Paging的Database列表已经完成,是否是很是简单呢?若是须要完整代码能够查看Github
上面是经过Room来获取数据,但咱们须要知道的是,Room之因此简单是由于它会帮咱们本身实现许多数据库相关的逻辑代码,让咱们只需关注与本身业务相关的逻辑便可。而这其中与Paging相关的是对DataSource与DataSource.Factory的具体实现。
可是咱们实际开发中数据绝大多数来自于网络,因此DataSource与DataSource.Factory的实现仍是要咱们本身来啃。
所幸的是,对于DataSource的实现,Paging已经帮咱们提供了三个很是全面的实现,分别是:
PositionalDataSource相信已经有点印象了吧,Room中默认帮我实现的就是经过PositionalDataSource来获取数据库中的数据的。
接下来咱们经过使用最广的PageKeyedDataSource来实现网络数据。
基于Databases的三步,咱们这里将它的第一步拆分为两步,因此咱们只需四步就能实现Paging对网络数据的处理。
咱们自定义的DataSource须要实现PageKeyedDataSource,实现了以后会有以下三个方法须要咱们去实现
class NewsDataSource(private val newsApi: NewsApi, private val domains: String, private val retryExecutor: Executor) : PageKeyedDataSource<Int, ArticleModel>() { override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, ArticleModel>) { // 初始化第一页数据 } override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, ArticleModel>) { // 加载下一页数据 } override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, ArticleModel>) { // 加载前一页数据 } }
其中loadBefore暂时用不到,由于我这个实例是获取新闻列表,因此只须要loadInitial与loadAfter便可。
至于这两个方法的具体实现,其实没什么多说的,根据你的业务要求来便可,这里要说的是,数据获取完毕以后要回调方法第二个参数callback的onResult方法。例如loadInitial:
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, ArticleModel>) { initStatus.postValue(Loading("")) CompositeDisposable().add(getEverything(domains, 1, ArticleListModel::class.java) .subscribeWith(object : DisposableObserver<ArticleListModel>() { override fun onComplete() { } override fun onError(e: Throwable) { retry = { loadInitial(params, callback) } initStatus.postValue(Error(e.localizedMessage)) } override fun onNext(t: ArticleListModel) { initStatus.postValue(Success(200)) callback.onResult(t.articles, 1, 2) } })) }
在onNext方法中,咱们将获取的数据填充到onResult方法中,同时传入了以前的页码previousPageKey(初始化为第一页)与以后的页面nextPageKey,nextPageKey天然是做用于loadAfter方法。这样咱们就能够在loadAfter中的params参数中获取到:
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, ArticleModel>) { loadStatus.postValue(Loading("")) CompositeDisposable().add(getEverything(domains, params.key, ArticleListModel::class.java) .subscribeWith(object : DisposableObserver<ArticleListModel>() { override fun onComplete() { } override fun onError(e: Throwable) { retry = { loadAfter(params, callback) } loadStatus.postValue(Error(e.localizedMessage)) } override fun onNext(t: ArticleListModel) { loadStatus.postValue(Success(200)) callback.onResult(t.articles, params.key + 1) } })) }
这样DataSource就基本上完成了,接下来要作的是,实现DataSource.Factory来生成咱们自定义的DataSource
以前咱们就已经说起到,DataSource.Factory只有一个abstract方法,咱们只需实现它的create方法来建立自定义的DataSource便可:
class NewsDataSourceFactory(private val newsApi: NewsApi, private val domains: String, private val executor: Executor) : DataSource.Factory<Int, ArticleModel>() { val dataSourceLiveData = MutableLiveData<NewsDataSource>() override fun create(): DataSource<Int, ArticleModel> { val dataSource = NewsDataSource(newsApi, domains, executor) dataSourceLiveData.postValue(dataSource) return dataSource } }
嗯,代码就是这么简单,这一步也就完成了,接下来要作的是将pagedList进行LiveData封装。
这里与Database不一样的是,并无直接在ViewModel中经过DataSource.Factory来获取pagedList,而是进一步使用Repository进行封装,统一经过sendRequest抽象方法来获取NewsListingModel的封装结果实例。
data class NewsListingModel(val pagedList: LiveData<PagedList<ArticleModel>>, val loadStatus: LiveData<LoadStatus>, val refreshStatus: LiveData<LoadStatus>, val retry: () -> Unit, val refresh: () -> Unit) sealed class LoadStatus : BaseModel() data class Success(val status: Int) : LoadStatus() data class NoMore(val content: String) : LoadStatus() data class Loading(val content: String) : LoadStatus() data class Error(val message: String) : LoadStatus()
因此Repository中的sendRequest返回的将是NewsListingModel,它里面包含了数据列表、加载状态、刷新状态、重试与刷新请求。
class NewsRepository(private val newsApi: NewsApi, private val domains: String, private val executor: Executor) : BaseRepository<NewsListingModel> { override fun sendRequest(pageSize: Int): NewsListingModel { val newsDataSourceFactory = NewsDataSourceFactory(newsApi, domains, executor) val newsPagingList = newsDataSourceFactory.toLiveData( pageSize = pageSize, fetchExecutor = executor ) val loadStatus = Transformations.switchMap(newsDataSourceFactory.dataSourceLiveData) { it.loadStatus } val initStatus = Transformations.switchMap(newsDataSourceFactory.dataSourceLiveData) { it.initStatus } return NewsListingModel( pagedList = newsPagingList, loadStatus = loadStatus, refreshStatus = initStatus, retry = { newsDataSourceFactory.dataSourceLiveData.value?.retryAll() }, refresh = { newsDataSourceFactory.dataSourceLiveData.value?.invalidate() } ) } }
接下来ViewModel中就相对来就简单许多了,它须要关注的就是对NewsListingModel中的数据进行分离成单个LiveData对象便可,因为自己其成员就是LiveDate对象,因此分离也是很是简单。分离是为了以便在Activity进行observe观察。
class NewsVM(app: Application, private val newsRepository: BaseRepository<NewsListingModel>) : AndroidViewModel(app) { private val newsListing = MutableLiveData<NewsListingModel>() val adapter = NewsAdapter { retry() } val newsLoadStatus = Transformations.switchMap(newsListing) { it.loadStatus } val refreshLoadStatus = Transformations.switchMap(newsListing) { it.refreshStatus } val articleList = Transformations.switchMap(newsListing) { it.pagedList } fun getData() { newsListing.value = newsRepository.sendRequest(20) } private fun retry() { newsListing.value?.retry?.invoke() } fun refresh() { newsListing.value?.refresh?.invoke() } }
Adapter部分与Database的基本相似,主要也是须要实现DiffUtil.ItemCallback,剩下的就是正常的Adapter实现,我这里就再也不多说了,若是须要的话请阅读源码
最后的observe代码
private fun addObserve() { newsVM.articleList.observe(this, Observer { newsVM.adapter.submitList(it) }) newsVM.newsLoadStatus.observe(this, Observer { newsVM.adapter.updateLoadStatus(it) }) newsVM.refreshLoadStatus.observe(this, Observer { refresh_layout.isRefreshing = it is Loading }) refresh_layout.setOnRefreshListener { newsVM.refresh() } newsVM.getData() }
Paging封装的仍是很是好的,尤为是项目中对RecyclerView很是依赖的,仍是效果不错的。固然它的优势也是它的局限性,这一点也是没办法的事情。
但愿你经过这篇文章可以熟悉运用Paging,若是这篇文章对你有所帮助,你能够顺手关注一波,这是对我最大的鼓励!
该库的目的是结合详细的Demo来全面解析Android相关的知识点, 帮助读者可以更快的掌握与理解所阐述的要点