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 {
        var viewModel: ViewModel? = null
        var classname = modelClass.name
        try {
            //通过反射来生产不同的ViewModel实例
            viewModel =  Class.forName(classname).getConstructor(JokeDao::class.java).newInstance(jokeDao) as ViewModel
        }catch (e:Exception){
            e.printStackTrace()
        }
        return viewModel 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。
在这里插入图片描述
在这里插入图片描述

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页