Paging 是 Android 新出的 Component,可以快速開發從網路批次載入的需求。
此範例是 CodeLab 的例子,發現到目前為止寫了好多 髒code(???
由於沒有研究過 Retrofit,所以花了點時間研究~~
相信大家都懂了 LiveData 在幹嘛惹
那什麼是 Paging 呢? 可以直接觀看官方的介紹
簡單來說,當使用者往下滑 list 會繼續加載資料,CodeLab 的範例有往下加載,但是如果資料 超級多的話 那個 list 遲早會爆掉的,我目前只有照著 CodeLab 的範例去做,我覺得 這部分還需要研究研究。
先來說說此架構:
ViewModel:持有資料的物件,會呼叫 Repository 去 Search 資料與設定 Callback。
Repository:知道如何跟誰存取資料的物件。
GithubService:知道如何與 Github Api 要資料的物件。
GithubLocalCache:在本地端的快取(資料來源是從 Github Api 來的),提供離線查詢。
RepoBoundaryCallback:當使用者滑到資料底部的時候,他會負責再去跟 GithubService 與 GithubLocalCache 存取資料,跟使用者說 這裡還有資料哦~~
Injection:由於 ViewModel , Repository , RepoBoundaryCallback 他們都具有 dependency 的關係,Injection 這個動作如果在 Activity 做的話,會讓 Activity 知道太多物件,所以獨立出來一個 object 做這件事情。
為了避免無腦的使用 notifyDataSetChanged() ,google 推出這個東西( PagedListAdapter)可以比較資料的差異,具題內容 下次再來寫好惹XDD。
首先先看到 Activity
viewModel.repos.observe(this, Observer<PagedList<Repo>> {
Log.d("Activity", "list: ${it?.size}")
showEmptyList(it?.size == 0)
adapter.submitList(it)
})
當 ViewModel 有資料變動的時候,會呼叫 Adapter 去更動資料,這裡用到的是 submitList,不是 notifyDataSetChanged()!!
當 trigger EditText 之後的動作,叫 ViewModel 去搜尋資料
viewModel.searchRepo(it.toString())
adapter.submitList(null)
ViewModel 進而會觸發 Repository(queryLiveData.postValue(queryString))
private val repoResult: LiveData<RepoSearchResult> = Transformations.map(queryLiveData, {
repository.search(it)
})
val repos: LiveData<PagedList<Repo>> = Transformations.switchMap(repoResult, { it ->
it.data
})
val networkErrors: LiveData<String> = Transformations.switchMap(repoResult, { it -> it.networkErrors })fun searchRepo(queryString: String) {
Log.d("ViewModel ","search again")
queryLiveData.postValue(queryString)
}
在 Repository 中的 search 會設定 Callback,當 list 滑到底部的時候,會藉由 DataFactory 去 trigger Callback 以便撈取更多資料。
fun search(query: String): RepoSearchResult {
Log.d("GithubRepository", "New query: $query")
lastRequestedPage = 1
// requestAndSaveData(query)
//get data from the local cache
val dataSourceFactory = cache.reposByName(query)
//Construct the boundary callback
val boundaryCallback = RepoBoundaryCallback(query, service, cache)
var networkErrors = boundaryCallback.networkErrors
val data = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE)
.setBoundaryCallback(boundaryCallback)
.build()
return RepoSearchResult(data, networkErrors)
}
Callback 的動作
class RepoBoundaryCallback(
private val query: String,
private val service: GithubService,
private val cache: GithubLocalCache
) : PagedList.BoundaryCallback<Repo>() {
private var lastRequestedPage = 1
// LiveData of network errors
val networkErrors = MutableLiveData<String>()
//avoid triggering multiple requests in the same time
private var isRequestInProgress = false
override fun onZeroItemsLoaded() {
Log.d("Callback ", "ItemLoad")
requestAndSaveData(query)
}
override fun onItemAtEndLoaded(itemAtEnd: Repo) {
Log.d("Callback ", "ItemEnd")
requestAndSaveData(query)
}
private fun requestAndSaveData(query: String) {
if (isRequestInProgress) return
isRequestInProgress = true
searchRepo(service, query, lastRequestedPage, NETWORK_PAGE_SIZE, { repos ->
cache.insert(repos, {
lastRequestedPage++
isRequestInProgress = false
})
}, { error ->
networkErrors.postValue(error)
isRequestInProgress = false
})
}
companion object {
private const val NETWORK_PAGE_SIZE = 50
}
}