Paging Library: Database + Network

原文地址:https://proandroiddev.com/paging-library-database-network-c8c3185cfe3f

在开发过程中有一个很常见的场景就是从数据库或者网络中加载数据时,有大量的实体不能一次被加载。

Only Database,Only Network

如果我们要从数据库或者网络中加载数据,使用分页的方式就不会那么复杂了,对于数据库来说,推荐使用Room with Paging Library,并且可以在网上找到大量的解决方案。对于网络加载来说,推荐使用Paging LibraryPaginate

Database + Network

但是困难的是,如何将两者结合在一起:

  1. 首先我们不能等待网络的返回,应该使用数据库的缓存数据来渲染UI,这意味着,当我们请求第一页数据时不能只是向服务器发送请求并等待他返回。我们应该直接从数据库获取数据并且显示它,然后当网络有数据返回的时候,我们再去更新数据库中的数据。
  2. 第二,如果远程服务器上的某条数据被删除了,你将如何注意到这个。

Paging Library对于解决从数据库和网络加载数据有自己的解决方案,就是使用BoundaryCallback,你可以添加自己的BoundaryCallback,他会根据某些事件通知你。他提供了三个方法用来覆盖:

  1. onZeroItemsLoaded()从PageList的数据源(Room数据库)返回零条结果时调用。
  2. onItemAtFrontLoaded(T itemAtFront)加载PageList前面的数据项时被调用。
  3. onItemAtEndLoaded(T itemAtEnd)加载PageList后面的数据项时被调用。

要解决的问题

  1. 使用Paging Library来观察数据库
  2. 观察Recclerview以了解何时需要向服务器请求数据。

为了演示,此处使用Person的实体类:

@Entity(tableName = "persons")
data class Person(
    @ColumnInfo(name = "id") @PrimaryKey val id: Long,
    @ColumnInfo(name = "name") val name: String,
    @ColumnInfo(name = "update_time") val updateTime: Long
)

观察数据库

在dao中定义一个方法用来观察数据库并且返回DataSource.Factory<Int,Person>

@Dao
interface PersonDao {
    @Query("SELECT * FROM persons ORDER BY update_time")
    fun selectPaged(): DataSource.Factory<Int, Person>
}

现在在ViewModel中我们将使用工厂构建一个PageList。

class PersonsViewModel(private val dao: PersonDao) : ViewModel() {
    val pagedListLiveData : LiveData<PagedList<Person>> by lazy {
        val dataSourceFactory = personDao.selectPaged()
        val config = PagedList.Config.Builder()
                .setPageSize(PAGE_SIZE)
                .build()
        LivePagedListBuilder(dataSourceFactory, config).build()
    }
}

在我们的view中可以观察paged list

class PersonsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_persons)

        viewModel.pagedListLiveData.observe(this, Observer{
            pagedListAdapter.submitList(it)
        })
    }
}

观察RecyclerView

现在我们要做的就是观察list,并且根据list的位置去请求服务器为我们提供相应页面的数据。为了观察RecyclerView的位置我们可以使用一个简单的库Paginate

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_persons)

        viewModel.pagedListLiveData.observe(this, Observer{
            pagedListAdapter.submitList(it)
        })
        Paginate.with(recyclerView, this).build()
    }
    override fun onLoadMore() {
        // send the request to get corresponding page
    }
    override fun isLoading(): Boolean = isLoading
    override fun hasLoadedAllItems(): Boolean = hasLoadedAllItems
}

正如你所看到的那样,我们将recyclerview与Paginate绑定,有三个回调函数,isLoading返回网络状态,hasLoadedAllItems用来展示是否已经到最后一页,并且没有更多数据要从服务器加载,最重要的方法就是实现onLoadMore()方法。
在这一部分有三个事情需要做:

  1. 基于recyclerview的位置,请求服务器来返回正确的数据页。
  2. 使用从server获取到的新的数据来更新数据库,并且导致PageList更新并显示新的数据,不要忘了我们正在观察数据。
  3. 如果请求失败我们展示错误。
override fun onLoadMore() {
    if (!isLoading) {
        isLoading = true
        viewModel.loadPersons(page++).observe(this, Observer { response ->
            isLoading = false
            if (response.isSuccessful()) {
                hasLoadedAllItems = response.data.hasLoadedAllItems
            } else {
                showError(response.errorBody())
            }
        })
    }
}
class PersonsViewModel(
        private val dao: PersonDao,
        private val networkHelper: NetworkHelper
) : ViewModel() {
fun loadPersons(page: Int): LiveData<Response<Pagination<Person>>> {
        val response = 
                MutableLiveData<Response<Pagination<Person>>>()
        networkHelper.loadPersons(page) {
            dao.updatePersons(
                    it.data.persons,
                    page == 0,
                    it.hasLoadedAllItems)
            response.postValue(it)
        }
        return response
    }
}

正如看到的那样,从网络获取数据并更新到数据库中。

@Dao
interface PersonDao {
    @Transaction
    fun persistPaged(
            persons: List<Person>,
            isFirstPage: Boolean,
            hasLoadedAllItems: Boolean) {
        val minUpdateTime = if (isFirstPage) {
            0
        } else {
            persons.first().updateTime
        }

        val maxUpdateTime = if (hasLoadedAllItems) {
            Long.MAX_VALUE
        } else {
            persons.last().updateTime
        }

        deleteRange(minUpdateTime, maxUpdateTime)
        insert(persons)
    }
    
    @Query("DELETE FROM persons WHERE
            update_time BETWEEN
            :minUpdateTime AND :maxUpdateTime")
    fun deleteRange(minUpdateTime: Long, maxUpdateTime: Long)
    @Insert(onConflict = REPLACE)
    fun insert(persons: List<Person>)
}

首先,我们在dao中删除updateTime在服务器返回的列表中第一个和最后一个人之间的所有人员,然后将列表插入数据库。这个是为了确保在服务器上删除的任何人同时能够在本地数据库删除。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容