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相關的知識點, 幫助讀者可以更快的掌握與理解所闡述的要點