今天要來實作使用 Paging 來完成一個簡單的列表顯示,且必須透過網路請求取得數據。
添加依賴
dependencies {
.
.
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
}
先看 API :
https://free-nba.p.rapidapi.com/players?page=1
發出 GET 請求後返回一個 List
其中帶入 ?page=1
來取得第一頁資料,帶入 ?page=2
來取得第二頁資料,依此類推。
這個 page 需要自己計算,回來的 response 不會有相關資訊,所以這邊用 PageKeyedDataSource
來當範例。
interface AllPlayerApi {
companion object {
private val retrofit = Retrofit
.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://free-nba.p.rapidapi.com/")
.build()
val api = retrofit.create(AllPlayerApi::class.java)
}
@GET("players")
fun getAllPlayer(@Query("page") page: Int): Call<AllPlayerData>
}
使用之前的 Retrofit 來取得資料,呼叫 getAllPlayer
便可以取得我們想要的數據 Call<AllPlayerData>
data class AllPlayerData(
@SerializedName("data")
val dataItemList: List<DataItem>
)
class PagingKeyDataSource : PageKeyedDataSource<Int, DataItem>() {
private var page = 1
val api = AllPlayerApi.api
val call = api.getAllPlayer(page)
override fun loadInitial(params: LoadInitialParams<Int>,
callback: LoadInitialCallback<Int, DataItem>) {
call.enqueue(object : Callback<AllPlayerData> {
override fun onFailure(call: Call<AllPlayerData>?, t: Throwable?) { }
override fun onResponse(call: Call<AllPlayerData>?,
response: Response<AllPlayerData>) {
callback.onResult(response.body()!!.dataItemList!!, null, page)
}
})
}
override fun loadAfter(params: LoadParams<Int>,
callback: LoadCallback<Int, DataItem>) {
call.enqueue(object : Callback<AllPlayerData> {
override fun onFailure(call: Call<AllPlayerData>?, t: Throwable?) { }
override fun onResponse(call: Call<AllPlayerData>?,
response: Response<AllPlayerData>) {
callback.onResult(response.body()!!.dataItemList!!, page)
page += 1
}
})
}
override fun loadBefore(params: LoadParams<Int>,
callback: LoadCallback<Int, DataItem>) { }
}
PagingKeyDataSource 這個類別繼承 PagingKeyDataSource
,後面帶入 <Int, DataItem>,Int 代表下一頁的 key,也就是 ?page=1
這個參數,後面的 Value 就是要顯示在列表的 Item 物件
這裡需要覆寫三個方法 loadInitial
、loadBefore
、loadAfter
看字面意思應該就知道要幹嘛了loadInitial
只會在初始化跑一次,在裡面去實作取資料的動作,並在取得資料成功後用 LoadCallback
把資料帶回,並在 callback 回去後把我們自行計算的 page +1。
callback.onResult(response.body()!!.dataItemList!!, null, page)
使用者往下滑到沒有資料時,會進入 loadAfter
,再去取得下一頁的資料,裡面做的事其實跟 loadInitial
是一樣的,如果沒有資料時就在 onFailure 裡面另外處理就可以了。
至於 loadBefore
是向前一個分頁加載數據,沒有要這個功能的話就空著就可以了。
class DataSourceFactory : DataSource.Factory<String, DataItem>() {
private val sourceLiveData = MutableLiveData<PagingDataSource>()
override fun create(): DataSource<String, DataItem> {
val source = PagingDataSource()
sourceLiveData.postValue(source)
return source
}
}
定義好 DataSource 後統一藉由 DataSourceFactory 去拿取想要的 DataSource,這樣之後如果要把資料的來源換成本地的資料庫或是其他來源會比較方便。
在 create 裡面把 DataSource 做好初始化,以及資料變化時的動作,因為是使用 MVVM 架構,所以 Data 必須用 MutableLiveData
包起來回傳。
到這邊已經完成最麻煩的資料來源的部分了,接著只要藉由 LiveData 的觀察者模式去觀察這個 DataSource,並在發生變化時通知 adapter !
class PagingAdapter : PagedListAdapter<DataItem, PagingHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagingHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.item_paging, parent, false)
return PagingHolder(view)
}
override fun onBindViewHolder(holder: PagingHolder, position: Int) {
val data = getItem(position)
data?.let {
holder.setResult(data)
}
}
companion object DiffCallback : DiffUtil.ItemCallback<DataItem>() {
override fun areItemsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem.id == newItem.id
}
}
}
還記得之前練習 DataBinding 使用的 ListAdapter
嗎, PagedListAdapter
和他非常相似。
在 onBindViewHolder
裡面呼叫 getItem
就可以取得 Paging 幫我們自動計算得到的 Item
,並丟進一個 DiffUtil.ItemCallback
去判斷更換數據的邏輯,提高效能。
這裡的 ViewHolder 就是普通的 ViewHolder,就不貼惹。
class PagingViewModel: ViewModel() {
private val sourceFactory by lazy {
DataSourceFactory()
}
val pagingDataItems: LiveData<PagedList<DataItem>> by lazy {
sourceFactory.toLiveData(6, null)
}
}
初始化 DataSourceFactory 後,Paging 提供了一個與 LiveData 整合的 fun toLiveData
,可以把 DataSource 轉成 LiveData
。
後面填入的是每一次要載入的 pageSize 和 initialLoadKey(不知道要幹嘛,我填 null)
終於來到最後一哩路
在 Activity
viewModel.pagingDataItems.observe(this, Observer {
adapter.submitList(it)
})
透過 submitList
把資料傳給 adapter,就可以跑跑看了~
之前在處理分頁時的卡頓感不見了,列表瀏覽變得更滑順流暢,且效能更好。
到這邊完成了 Paging 最常見的使用(NetWork Datasource)。
有任何問題或講得不清楚的地方歡迎留言和我討論。
更歡迎留言糾正我任何說錯的地方!
下一篇:Paging (三) (NetWork + Database)Datasource