Android_Jetpack:Paging元件之BoundaryCallback的使用
Paging元件除了單純地支援網路、資料庫為資料來源外,還支援網路+資料庫的架構方式,這就用到了BoundaryCallback。本文我們會使用PositionalDataSource方式載入資料,來簡化多資料來源應用的複雜度。
BoundaryCallback的使用流程如下:
通過流程圖可知,資料庫是頁面的唯一資料來源:頁面訂閱了資料庫的變化,當資料庫中的資料發生變化時,會直接反映到頁面上。
- 若資料庫中沒有資料,會通知BoundaryCallback中得到onZeroItemsLoaded方法;若資料庫中有資料,則當使用者滑動到RecyclerView底部時,且資料庫中的資料全部載入完畢時,會通知BoundaryCallback中的OnItemAtEndLoad方法。
- 當BoundaryCallback中的回撥方法被呼叫時,需要在該方法內開啟工作執行緒請求網路資料。
- 當網路資料成功載入回來,並不直接展示資料,而是將其寫入資料庫。
- 由於已經設定好了頁面對資料庫的訂閱,當資料庫有新資料寫入時,會自動更新到頁面。
- 當需要重新整理資料時,可以通過頁面下拉重新整理功能在下拉過程中情況資料。當資料庫被清空時,由於資料庫發生變化,進而再次觸發步驟1,通知BoundaryCallback重新獲取資料。
接下來將在Android_Jetpack:Paging元件之PageKeyedDataSource的MVVM使用的基礎上,使用BoundaryCallback和Room元件進行展示。
①引入依賴,建立room資料庫和Model類,以及針對Model類實現對應的Dao檔案,以方便對資料的增刪改查。
//按照"最新笑話"定義
data class JokeResponse(val reason:String, val result:Result,@SerializedName("error_code") val errorCode :Int) {
data class Result(val data:List<Joke>)
@Entity(tableName = "joke")
data class Joke(val content:String, val hashId:String,val unixtime:Long,val updatetime:String){
@PrimaryKey(autoGenerate = true)
var id:Long = 0
}
}
@Dao
interface JokeDao {
@Insert
fun insertJokes(jokes: List<JokeResponse.Joke>)
@Query("DELETE FROM joke")
fun clear()
@Query("SELECT * FROM joke")
fun getJokeList(): DataSource.Factory<Int,JokeResponse.Joke>
}
注意getJokeList()方法返回的是一個DataSource.Factory,這樣就可以實現資料庫的訂閱。
②實現BoundaryCallback。
class JokeBoundaryCallback(private val jokeDao: JokeDao):PagedList.BoundaryCallback<JokeResponse.Joke> (){
private val sharedPreferences: SharedPreferences = context.getSharedPreferences("Joke", Context.MODE_PRIVATE)
override fun onZeroItemsLoaded() {
super.onZeroItemsLoaded()
//載入第一頁資料,資料庫為空時,會回撥該方法
JokeBoundaryCallbackViewModel.FIRST_PAGE = 1
searchJokes()
}
override fun onItemAtFrontLoaded(itemAtFront: JokeResponse.Joke) {
super.onItemAtFrontLoaded(itemAtFront)
//載入第一個資料
//暫時用不上,什麼都不用做
}
override fun onItemAtEndLoaded(itemAtEnd: JokeResponse.Joke) {
super.onItemAtEndLoaded(itemAtEnd)
//載入最後一個資料,當使用者滑動到頁面的最下方時且資料庫中的資料已經全部載入完畢,會回撥該方法
//itemAtEnd是資料庫中最後一條資料
readPage()
//介面實測最多20頁,之後都只顯示第20頁的內容,因此20頁以後不載入
//注意,這只是介面不支援的權宜之計,實際專案中,應該使Joke含有當前所在頁數字段
if (JokeBoundaryCallbackViewModel.FIRST_PAGE<20) {
searchJokes()
}
}
//查詢資料
private fun searchJokes(){
val job = Job()
val scope = CoroutineScope(job)
scope.launch {
val jokeInfoResponse = RetrofitNetwork.searchJokes(JokeBoundaryCallbackViewModel.FIRST_PAGE,JokeBoundaryCallbackViewModel.PAGE_SIZE)
Log.d("jokeInfoResponse",jokeInfoResponse.toString())
if (jokeInfoResponse.errorCode == 0){
val jokes= jokeInfoResponse.result.data
insertJokes(jokes)
JokeBoundaryCallbackViewModel.FIRST_PAGE+=1
savePage()
}else{
}
}
}
//插入資料
private fun insertJokes(joks:List<JokeResponse.Joke>){
thread {
jokeDao.insertJokes(joks)
}
}
//儲存最後頁數
private fun savePage(){
val editor:SharedPreferences.Editor = sharedPreferences.edit()
editor.putInt("joke_max_page", JokeBoundaryCallbackViewModel.FIRST_PAGE)
editor.apply()
editor.commit()
}
//讀取最後頁數
private fun readPage() {
JokeBoundaryCallbackViewModel.FIRST_PAGE = sharedPreferences.getInt("joke_max_page", 1)
}
}
在BoundaryCallback中有三個回撥方法,具體功能已經寫在註釋內。需要注意的是,由於onItemAtEndLoaded()方法的引數返回的是資料庫中最後一條資料,所以我們手動儲存了當前的頁碼,以便於下次進入時能夠從沒有寫入資料庫的頁數中獲取,而非從頭開始。
③實現ViewModel。
class JokeBoundaryCallbackViewModel (jokeDao: JokeDao) : ViewModel() {
companion object{
var FIRST_PAGE=1
const val PAGE_SIZE=10
}
var jokePagedList: LiveData<PagedList<JokeResponse.Joke>>
init {
//Room元件對Paging元件提供原生支援,LivePagedListBuilder建立PagedList時可以直接將Room作為資料來源
jokePagedList =
LivePagedListBuilder<Int,JokeResponse.Joke>(jokeDao.getJokeList(), PAGE_SIZE)
.setBoundaryCallback(JokeBoundaryCallback(jokeDao))//將PagedList與BoundaryCallback關聯
.build()
}
}
④修改Activity,完成頁面呼叫。
class JokeViewModelFactory (private val jokeDao: JokeDao): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return JokeBoundaryCallbackViewModel(jokeDao) as T
}
}
class BoundaryCallbackTestMainActivity : AppCompatActivity() {
lateinit var jokeDao: JokeDao
lateinit var viewModel: JokeBoundaryCallbackViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_boundary_callback_test_main)
jokeDao = AppDatabase.getDatabase(this).jokeDao()
viewModel = ViewModelProvider(this, JokeViewModelFactory(jokeDao)).get(JokeBoundaryCallbackViewModel::class.java)
jokeRecyclerView.layoutManager = LinearLayoutManager(this)
jokeRecyclerView.setHasFixedSize(true)
jokeRecyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
val jokePagedListAdapter = JokePagedListAdapter(this)
viewModel.jokePagedList.observe(this, Observer { jokes ->
jokePagedListAdapter.submitList(jokes)
})
jokeRecyclerView.adapter = jokePagedListAdapter
}
}
其餘未展示的部分和之前一樣不變,參照Android_Jetpack:Paging元件之PageKeyedDataSource的MVVM使用即可。
執行一下,檢視LOG:首次進入頁面時獲取資料,上滑獲取下一頁的資料,退出重新進入到之前獲取過資料的部分,直接顯示資料庫內的資料,滑動到資料庫內沒有資料的頁數,獲取新頁數的資料。
⑤新增下拉重新整理功能。
下拉重新整理思路為在下拉的時候,清空資料庫表,由此觸發自動執行BoundaryCallback中onZeroItemsLoaded()方法。
a.在JokeBoundaryCallbackViewModel中新增方法
//重新整理資料
fun refresh(){
thread {
Log.d("refresh","refresh")
jokeDao.clear()
}
}
b.在佈局檔案新增下拉重新整理元件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/jokeRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
c.在BoundaryCallbackTestMainActivity中使用下拉重新整理元件
swipeRefreshLayout.setOnRefreshListener {
viewModel.refresh()
swipeRefreshLayout.isRefreshing = false
}
執行程式,下拉重新整理,檢視LOG,OK。
相關文章
- Android分頁元件Paging簡單使用Android元件
- Paging Library使用及原理
- 怎麼學習谷歌最新的元件--Jetpack?一文介紹paging的基本使用谷歌元件Jetpack
- uniapp使用z-paging外掛APP
- Android官方架構元件Paging:分頁庫的設計美學Android架構元件
- vue之router-view元件的使用VueView元件
- Android官方架構元件Paging-Ex:為分頁列表新增Header和FooterAndroid架構元件Header
- Vue自定義元件之v-model的使用Vue元件
- Jetpack 之 LifeCycle 元件使用詳解Jetpack元件
- Flutter元件之ClipRRect簡單使用Flutter元件
- C#控制元件之Repeater控制元件使用C#控制元件
- 反思|Android 列表分頁元件Paging的設計與實現:架構設計與原理解析Android元件架構
- Paging Library原始碼淺析原始碼
- Paging 3.0 簡介 | MAD Skills
- AdminLTE元件使用方法之control-sidebar元件IDE
- Animation元件的使用元件
- livewire 元件的使用元件
- 微信小程式之如何使用自定義元件封裝原生 image 元件微信小程式元件封裝
- vue 元件之間通訊eventBus使用方法Vue元件
- React之元件(component)之間的通訊React元件
- Container容器元件的使用AI元件
- HarmonyOS:Navigation元件的使用Navigation元件
- TornadoFx的TableView元件使用View元件
- Vue子元件與父元件之間的通訊Vue元件
- vue框架之自定義元件中使用v-modelVue框架元件
- ASP.NET Core知識之RabbitMQ元件使用(二)ASP.NETMQ元件
- 使用element-ui的el-tree元件入坑講解之setCheckedKeysUI元件
- Vue3學習(十五)之 級聯選擇元件Cascader的使用Vue元件
- vue元件之間的通訊Vue元件
- React - 元件之間的通訊React元件
- 元件之間的通訊LiveDataBus元件LiveData
- react複合元件的使用React元件
- Vue 元件的使用語法Vue元件
- React高階元件的使用React元件
- 拖拽元件:React DnD 的使用元件React
- 「技術雷達」之使用 Enzyme 測試 React(Native)元件React元件
- 孟老闆 Paging3 (二) 結合RoomOOM
- React之受控元件和非受控元件React元件