先說,今天的是一個失敗的案例。
因為一直找不出原因,一直在猶豫要不要放上來的時候,看到某位邦友的文章裡面提到心得筆記的話題,裡面有一篇文章大大的啟發了我 每一篇心得都有價值——為什麼初學者才更應該要寫心得筆記
裡面提到,在一開始學習新東西時一定都會有撞牆、卡關的地方,這時候反而更應該把這些點滴記錄下來,因為之後學會了、變強了,再也回不去當時那種矇矇懂懂的感覺,這時候如果有人也遇到同樣的問題,已經無法站在他們的角度去思考問題了。
所以決定把這次失敗的經驗記錄下來,也許未來有一天找出問題了再回來補上正確的,順便回憶一下初學時的撞牆期,也是一個不錯的經驗。
進入正題
把 DataSource 換成 (NetWork + Database),每次都會先去要 Database 的資料,如果 Database 沒東西再去呼叫 API 抓,一從網路上抓到資料回來後立刻存進 Database 裡面,這樣下次再開啟 APP 就會有 Database 的資料可以用了。
這是 Paging 在給 Item 設定數據時的回調,主要實作兩個方法
onZeroItemsLoaded
沒有 Item 有數據時呼叫這個方法,通常是第一次進入列表時調用。onItemAtEndLoaded
在讀到最後一個有數據的 Item 時呼叫,這時候會去跟 DataSource 要新的資料塞進接下來的 Item 裡。
這裡我參考 Google Sample Code 的寫法把呼叫 API 和存進 Room
的方法拉出來,整個 class 會長這樣
class PagingBoundaryCallback(context: Context) :
PagedList.BoundaryCallback<DataItem>() {
private var page = 1
private val api = AllPlayerApi.api
private val dao = DataItemDbHelper(context).getRoomDataItemDao()
override fun onZeroItemsLoaded() {
super.onZeroItemsLoaded()
api.getAllPlayer().enqueue(createWebserviceCallback())
}
override fun onItemAtEndLoaded(itemAtEnd: DataItem) {
super.onItemAtEndLoaded(itemAtEnd)
api.getAllPlayer(page).clone().enqueue(createWebserviceCallback())
}
private fun createWebserviceCallback(): Callback<AllPlayerData> {
return object : Callback<AllPlayerData> {
override fun onFailure(call: Call<AllPlayerData>?, t: Throwable?) { }
override fun onResponse(call: Call<AllPlayerData>?, response: Response<AllPlayerData>) {
insertItemsIntoDb(response)
page++
}
}
}
private fun insertItemsIntoDb(response: Response<AllPlayerData>) {
GlobalScope.launch {
response.body()!!.data!!.forEach {
dao.insert(it)
}
}
}
}
延續上一篇,因為 DataSource 的來源變成本地端的 Database,所以要修改一下 Repository,讓他從 Remote 拿資料變成和 Local Database 拿資料
class PagingRepository {
private lateinit var localDataSource: DataSource.Factory<Int, DataItem>
}
新增的這個 localDataSource
代表是從本地端資料庫的數據來源。
fun getDataItem(application: Application): LiveData<PagedList<DataItem>> {
val pagedListLiveData: LiveData<PagedList<DataItem>> by lazy {
localDataSource = DataItemDbHelper(application).getRoomDataItemDao().getAllDataItem()
val config = PagedList.Config.Builder()
.setPageSize(25)
.setEnablePlaceholders(false)
.build()
LivePagedListBuilder(localDataSource, config)
.setBoundaryCallback(PagingBoundaryCallback(application))
.build()
}
return pagedListLiveData
}
getDataItem 方法回傳 LiveData
,裡面可以透過 application 拿到由 Room
所持有的本地端數據,
並在這邊設定 Paging 的 Config,像是每一頁的數量,需不需要 Placeholder 等等...
再把剛剛的 BoundaryCallback
餵給 LivePagedListBuilder
。
這裡為什麼可以透過 dao 拿到 DataSource.Factory
呢?
多虧了 Room
和 Paging
整合的關係,直接把 Room
回傳的型態改成 DataSource.Factory
就可以了
@Dao
interface DataItemDao {
@Query("SELECT * FROM DATA_ITEM_ENTITY")
fun getAllDataItem(): DataSource.Factory<Int, DataItem>
}
修改完 Repository 後,因為要把資料存進 Room
,要透過 ViewModel 把 Context 傳給 Repository 用,還記得之前有一個 AndroidViewModel
可以拿到 Application 的 Context 嗎
class PagingViewModel(repository: PagingRepository, application: Application) :
AndroidViewModel(application) {
val pagedListLiveData = repository.getDataItem(application)
}
這樣算是完成了 NetWork + Database 的資料來源,不過在第一次開啟時,因為資料庫沒有任何資料,所以在 BoundaryCallback
裡面觸發 onZeroItemsLoaded
時會跟 API 要資料,這時候整個 List 會有跳動的感覺, 資料的排列也很奇怪。
但是第二次再開啟時的滑動就變得很順暢,打印 Log 後確定是有存進 Db 裡面的,代表後面這一段應該是有寫對(吧?
如果有人發現了我哪邊寫錯,可以在下方留言告訴我,解不了 Bug 實在是一件很煩躁的事
其他有任何問題或講得不清楚的地方歡迎留言和我討論。
更歡迎留言糾正我任何說錯的地方!