Paging Library: Database + Network

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

在開發(fā)過程中有一個很常見的場景就是從數(shù)據(jù)庫或者網(wǎng)絡中加載數(shù)據(jù)時笔宿,有大量的實體不能一次被加載。

Only Database,Only Network

如果我們要從數(shù)據(jù)庫或者網(wǎng)絡中加載數(shù)據(jù),使用分頁的方式就不會那么復雜了惠昔,對于數(shù)據(jù)庫來說或衡,推薦使用Room with Paging Library,并且可以在網(wǎng)上找到大量的解決方案鳞绕。對于網(wǎng)絡加載來說龙填,推薦使用Paging LibraryPaginate

Database + Network

但是困難的是螟左,如何將兩者結(jié)合在一起:

  1. 首先我們不能等待網(wǎng)絡的返回啡浊,應該使用數(shù)據(jù)庫的緩存數(shù)據(jù)來渲染UI觅够,這意味著胶背,當我們請求第一頁數(shù)據(jù)時不能只是向服務器發(fā)送請求并等待他返回。我們應該直接從數(shù)據(jù)庫獲取數(shù)據(jù)并且顯示它喘先,然后當網(wǎng)絡有數(shù)據(jù)返回的時候钳吟,我們再去更新數(shù)據(jù)庫中的數(shù)據(jù)。
  2. 第二窘拯,如果遠程服務器上的某條數(shù)據(jù)被刪除了红且,你將如何注意到這個。

Paging Library對于解決從數(shù)據(jù)庫和網(wǎng)絡加載數(shù)據(jù)有自己的解決方案涤姊,就是使用BoundaryCallback暇番,你可以添加自己的BoundaryCallback,他會根據(jù)某些事件通知你思喊。他提供了三個方法用來覆蓋:

  1. onZeroItemsLoaded()從PageList的數(shù)據(jù)源(Room數(shù)據(jù)庫)返回零條結(jié)果時調(diào)用壁酬。
  2. onItemAtFrontLoaded(T itemAtFront)加載PageList前面的數(shù)據(jù)項時被調(diào)用。
  3. onItemAtEndLoaded(T itemAtEnd)加載PageList后面的數(shù)據(jù)項時被調(diào)用恨课。

要解決的問題

  1. 使用Paging Library來觀察數(shù)據(jù)庫
  2. 觀察Recclerview以了解何時需要向服務器請求數(shù)據(jù)舆乔。

為了演示,此處使用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
)

觀察數(shù)據(jù)庫

在dao中定義一個方法用來觀察數(shù)據(jù)庫并且返回DataSource.Factory<Int,Person>

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

現(xiàn)在在ViewModel中我們將使用工廠構(gòu)建一個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

現(xiàn)在我們要做的就是觀察list希俩,并且根據(jù)list的位置去請求服務器為我們提供相應頁面的數(shù)據(jù)。為了觀察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綁定,有三個回調(diào)函數(shù)拖吼,isLoading返回網(wǎng)絡狀態(tài)鳞上,hasLoadedAllItems用來展示是否已經(jīng)到最后一頁,并且沒有更多數(shù)據(jù)要從服務器加載绿贞,最重要的方法就是實現(xiàn)onLoadMore()方法因块。
在這一部分有三個事情需要做:

  1. 基于recyclerview的位置,請求服務器來返回正確的數(shù)據(jù)頁籍铁。
  2. 使用從server獲取到的新的數(shù)據(jù)來更新數(shù)據(jù)庫涡上,并且導致PageList更新并顯示新的數(shù)據(jù),不要忘了我們正在觀察數(shù)據(jù)拒名。
  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
    }
}

正如看到的那樣,從網(wǎng)絡獲取數(shù)據(jù)并更新到數(shù)據(jù)庫中增显。

@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在服務器返回的列表中第一個和最后一個人之間的所有人員,然后將列表插入數(shù)據(jù)庫。這個是為了確保在服務器上刪除的任何人同時能夠在本地數(shù)據(jù)庫刪除糖权。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末堵腹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子星澳,更是在濱河造成了極大的恐慌疚顷,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禁偎,死亡現(xiàn)場離奇詭異腿堤,居然都是意外死亡,警方通過查閱死者的電腦和手機如暖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門笆檀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盒至,你說我怎么就攤上這事酗洒。” “怎么了妄迁?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵寝蹈,是天一觀的道長。 經(jīng)常有香客問我登淘,道長箫老,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任黔州,我火速辦了婚禮耍鬓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘流妻。我一直安慰自己牲蜀,他們只是感情好,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布绅这。 她就那樣靜靜地躺著涣达,像睡著了一般。 火紅的嫁衣襯著肌膚如雪证薇。 梳的紋絲不亂的頭發(fā)上度苔,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天,我揣著相機與錄音浑度,去河邊找鬼寇窑。 笑死,一個胖子當著我的面吹牛箩张,可吹牛的內(nèi)容都是我干的甩骏。 我是一名探鬼主播窗市,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼饮笛!你這毒婦竟也來了咨察?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤缎浇,失蹤者是張志新(化名)和其女友劉穎扎拣,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體素跺,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年誉券,在試婚紗的時候發(fā)現(xiàn)自己被綠了指厌。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡踊跟,死狀恐怖踩验,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情商玫,我是刑警寧澤箕憾,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站拳昌,受9級特大地震影響袭异,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜炬藤,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一御铃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沈矿,春花似錦上真、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至陵像,卻和暖如春就珠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蠢壹。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工嗓违, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人图贸。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓蹂季,卻偏偏與公主長得像冕广,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子偿洁,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容