天天看點

android studio 分頁,Android Paging codelab

1、介紹

你要建造什麼

在這個代碼庫中,您從一個示例應用程式開始,該應用程式已經顯示了GitHub存儲庫清單,從資料庫加載資料并且由網絡資料支援。 隻要使用者滾動并到達顯示清單的末尾,就會觸發新的網絡請求,并将其結果儲存在資料庫中。

您将通過一系列步驟添加代碼,在您進行時內建Paging庫元件。 這些元件在步驟2中描述。

你需要什麼

Android Studio 3.0或更高版本。

要熟悉以下架構元件:Room,LiveData,ViewModel以及“應用程式架構指南”中建議的架構。

2、設定您的環境

android studio 分頁,Android Paging codelab

您還可以在GitHub上檢視codelab。 初始狀态位于主分支上,并在解決方案分支上檢視解決方案。

3、分頁庫元件

分頁庫使您可以更輕松地在應用程式的UI中逐漸和優雅地加載資料。

應用程式架構指南提出了一個具有以下主要元件的架構:

本地資料庫,作為呈現給使用者的資料的單一事實來源以及使用者為更改該資料而采取的操作。

Web API服務

與資料庫和API服務一起使用的存儲庫,提供統一的資料接口

ViewModel,提供特定于UI的資料

UI,顯示ViewModel中資料的直覺表示

Paging庫可以處理所有這些元件并協調它們之間的互動,以便它可以從資料源中分頁内容并在UI中顯示該内容。

android studio 分頁,Android Paging codelab

此代碼庫向您介紹了Paging庫及其主要元件:

PagedList - 以異步方式在頁面中加載資料的集合。 PagedList可用于從您定義的源加載資料,并使用RecyclerView在UI中輕松呈現。

DataSource和DataSource.Factory - DataSource是将資料快照加載到PagedList的基類。 DataSource.Factory負責建立DataSource。

LivePagedListBuilder - 基于DataSource.Factory和PagedList.Config建構LiveData 。

BoundaryCallback - 當PagedList到達可用資料的末尾時發出信号。

PagedListAdapter - 一個RecyclerView.Adapter,它在RecyclerView中顯示來自PagedLists的分頁資料。 PagedListAdapter在加載頁面時監聽PagedList加載回調,并在收到新的PagedLists時使用DiffUtil計算細粒度更新。

在此代碼框中,您可以實作上述每個元件的示例。

4、工程概況

該應用程式允許您在GitHub中搜尋其名稱或描述包含特定單詞的存儲庫。 将顯示存儲庫清單,按降序排列,基于星号,然後按名稱顯示。 資料庫是UI顯示的資料的真實來源,它由網絡請求支援。

按名稱,通過RepoDao.reposByName中的LiveData對象檢索存儲庫清單。 每當來自網絡的新資料插入資料庫時,LiveData将再次使用查詢的整個結果發出。

目前實作有兩個記憶體/性能問題:

一次加載資料庫的整個repo表。

資料庫的整個結果清單儲存在記憶體中。

該應用程式遵循“應用程式架構指南”中推薦的體系結構,使用Room作為本地資料存儲。 以下是每個包裝中的内容:

api - 使用Retrofit包含Github API調用

db - 網絡資料的資料庫緩存

data - 包含存儲庫類,負責觸發API請求并在資料庫中儲存響應

ui - 包含與使用RecyclerView顯示活動相關的類

model - 包含Repo資料模型,它也是Room資料庫中的一個表; 和

RepoSearchResult,UI用于觀察搜尋結果資料和網絡錯誤的類

注意:GithubRepository和Repo類具有相似的名稱,但用途卻截然不同。 存儲庫類GithubRepository與代表GitHub代碼存儲庫的Repo資料對象一起使用。

5、使用PagedList以塊的形式加載資料

在我們目前的實作中,我們使用LiveData >從資料庫中擷取資料并将其傳遞給UI。 每當修改本地資料庫中的資料時,LiveData都會發出更新的清單。 List 的替代方案是PagedList 。 PagedList是List的一個版本,它以塊的形式加載内容。 與List類似,PagedList包含内容的快照,是以當通過LiveData傳遞PagedList的新執行個體時會發生更新。

建立PagedList時,它會立即加載第一個資料塊,并在将來加載了内容時會再次擴充,。 PagedList的大小是每次傳遞期間加載的項目數。 該類支援無限清單和具有固定數量元素的非常大的清單。

用PagedList 替換List 的出現次數:

RepoSearchResult是UI用于顯示資料的資料模型。 由于資料不再是LiveData >而是分頁,是以需要将其替換為LiveData >。 在RepoSearchResult類中進行此更改。

SearchRepositoriesViewModel使用來自GithubRepository的資料。 更改ViewModel公開的repos val的類型,從LiveData >更改為LiveData >。

SearchRepositoriesActivity從ViewModel觀察repos。 将List 中的觀察者類型更改為PagedList 。

viewModel.repos.observe(this, Observer> {

showEmptyList(it?.size == 0)

adapter.submitList(it)

})

6、定義分頁清單的資料源

agedList從源動态加載内容。 在我們的例子中,因為資料庫是UI的主要真實來源,它也代表了PagedList的來源。 如果您的應用直接從網絡擷取資料并在沒有緩存的情況下顯示資料,則發出網絡請求的類将成為您的資料源。

源由DataSource類定義。 要從可以更改的源(例如允許插入,删除或更新資料的源)中分頁資料,您還需要實作知道如何建立DataSource的DataSource.Factory。 每當更新資料集時,DataSource都會失效并通過DataSource.Factory自動重新建立。

Room持久性庫為與Paging庫關聯的資料源提供本機支援。 對于給定的查詢,Room允許您從DAO傳回DataSource.Factory并為您處理DataSource的實作。

更新代碼以從Room擷取DataSource.Factory:

RepoDao:更新reposByName()函數以傳回DataSource.Factory 。

fun reposByName(queryString: String): DataSource.Factory

GithubLocalCache使用此功能。 将reposByName函數的傳回類型更改為DataSource.Factory 。

7、建構和配置分頁清單

要建構和配置LiveData ,請使用LivePagedListBuilder。 除了DataSource.Factory,您還需要提供PagedList配置,其中包括以下選項:

由PagedList加載的頁面大小

加載到底有多遠

第一次加載時要加載的項目數

是否要将空項添加到PagedList,以表示尚未加載的資料。

更新GithubRepository以建構和配置分頁清單:

定義要由分頁庫檢索的每頁項目數。 在伴随對象中,添加另一個名為DATABASE_PAGE_SIZE的const val,并将其設定為20.然後,我們的PagedList将以20個項目的塊為機關從DataSource中分頁資料。

companion object {

private const val NETWORK_PAGE_SIZE = 50

private const val DATABASE_PAGE_SIZE = 20

}

注意:DataSource頁面大小應該是幾個螢幕的項目。 如果頁面太小,您的清單可能會閃爍,因為頁面内容未覆寫整個螢幕。 較大的頁面大小有利于加載效率,但可能會增加顯示更新的延遲。

在GithubRepository.search()方法中,進行以下更改:

删除lastRequestedPage初始化和對requestAndSaveData()的調用,但暫時不完全删除此函數。

建立一個新值以從cache.reposByName()中儲存DataSource.Factory:

// Get data source factory from the local cache

val dataSourceFactory = cache.reposByName(query)

在search()函數中,從LivePagedListBuilder構造資料值。 LivePagedListBuilder是使用dataSourceFactory和您之前定義的資料庫頁面大小構造的。

fun search(query: String): RepoSearchResult {

// Get data source factory from the local cache

val dataSourceFactory = cache.reposByName(query)

// Get the paged list

val data = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE).build()

// Get the network errors exposed by the boundary callback

return RepoSearchResult(data, networkErrors)

}

8、使RecyclerView擴充卡與PagedList一起使用

要将PagedList綁定到RecycleView,請使用PagedListAdapter。 每當加載PagedList内容時,PagedListAdapter都會收到通知,然後通知RecyclerView進行更新。

更新ReposAdapter以使用PagedList:

現在,ReposAdapter是一個ListAdapter。 使它成為PagedListAdapter:

class ReposAdapter : PagedListAdapter(REPO_COMPARATOR)

我們的應用程式最終編譯! 運作它,看看它是如何工作的。

9、觸發網絡更新

目前,我們使用附加到RecyclerView的OnScrollListener來了解何時觸發更多資料。 不過,我們可以讓Paging庫處理清單滾動。

删除自定義滾動處理:

SearchRepositoriesActivity:删除setupScrollListener()方法及其所有引用

SearchRepositoriesViewModel:删除listScrolled()方法和随播對象

删除自定義滾動處理後,我們的應用程式具有以下行為:

每當我們滾動時,PagedListAdapter都會嘗試從特定位置擷取項目。

如果該索引處的項目尚未加載到PagedList中,則Paging庫會嘗試從資料源擷取資料。

當資料源沒有任何更多資料要提供給我們時,會出現問題,因為從初始加載資料傳回零項或者因為我們已經從DataSource到達資料的末尾。 要解決此問題,請實作BoundaryCallback。 當這兩種情況發生時,該類會通知我們,是以我們知道何時請求更多資料。 由于我們的DataSource是一個由網絡資料支援的Room資料庫,是以回調告訴我們應該從API請求更多資料。

使用BoundaryCallback處理資料加載:

在資料包中,建立一個名為RepoBoundaryCallback的新類,該類實作PagedList.BoundaryCallback 。 因為此類處理特定查詢的網絡請求和資料庫資料儲存,是以将以下參數添加到構造函數:查詢字元串,GithubService和GithubLocalCache。

在RepoBoundaryCallback中,重寫onZeroItemsLoaded()和onItemAtEndLoaded()。

class RepoBoundaryCallback(

private val query: String,

private val service: GithubService,

private val cache: GithubLocalCache

) : PagedList.BoundaryCallback() {

override fun onZeroItemsLoaded() {

}

override fun onItemAtEndLoaded(itemAtEnd: Repo) {

}

}

将以下字段從GithubRepository移動到RepoBoundaryCallback:isRequestInProgress,lastRequestedPage和networkErrors。

從networkErrors中删除可見性修飾符。 為其建立支援屬性,并将networkErrors的類型更改為LiveData 。 我們需要進行此更改,因為在内部,在RepoBoundaryCallback類中,我們可以使用MutableLiveData,但在類之外,我們隻公開一個LiveData對象,其值無法修改。

// keep the last requested page.

// When the request is successful, increment the page number.

private var lastRequestedPage = 1

private val _networkErrors = MutableLiveData()

// LiveData of network errors.

val networkErrors: LiveData

get() = _networkErrors

// avoid triggering multiple requests in the same time

private var isRequestInProgress = false

在RepoBoundaryCallback中建立一個伴随對象,并在那裡移動GithubRepository.NETWORK_PAGE_SIZE常量。

将GithubRepository.requestAndSaveData()方法移動到RepoBoundaryCallback。

更新requestAndSaveData()方法以使用支援屬性_networkErrors。

每當Paging資料源通知我們源中沒有項目(調用RepoBoundaryCallback.onZeroItemsLoaded()時)或者資料源中的最後一項已加載時,我們應該從網絡請求資料并将其儲存在緩存中( 當調用RepoBoundaryCallback.onItemAtEndLoaded()時)。 是以,從onZeroItemsLoaded()和onItemAtEndLoaded()調用requestAndSaveData()方法:

override fun onZeroItemsLoaded() {

requestAndSaveData(query)

}

override fun onItemAtEndLoaded(itemAtEnd: Repo) {

requestAndSaveData(query)

}

在建立PagedList時更新GithubRepository以使用BoundaryCallback:

在search()方法中,使用查詢,服務和緩存構造RepoBoundaryCallback。

在search()方法中建立一個值,該值維護對RepoBoundaryCallback- 發現的網絡錯誤的引用。

将邊界回調設定為LivePagedListBuilder。

fun search(query: String): RepoSearchResult {

Log.d("GithubRepository", "New query: $query")

// Get data source factory from the local cache

val dataSourceFactory = cache.reposByName(query)

// Construct the boundary callback

val boundaryCallback = RepoBoundaryCallback(query, service, cache)

val networkErrors = boundaryCallback.networkErrors

// Get the paged list

val data = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE)

.setBoundaryCallback(boundaryCallback)

.build()

// Get the network errors exposed by the boundary callback

return RepoSearchResult(data, networkErrors)

}

從GithubRepository中删除requestMore()函數

而已! 使用目前設定,Paging庫元件是在正确的時間觸發API請求,将資料儲存在資料庫中以及顯示資料的元件。 是以,運作應用程式并搜尋存儲庫。

10、包裝

現在我們添加了所有元件,讓我們退一步看看一切如何協同工作。

DataSource.Factory(由Room實作)建立DataSource。 然後,LivePagedListBuilder使用傳入的DataSource.Factory,BoundaryCallback和PagedList配置建構LiveData 。 此LivePagedListBuilder對象負責建立PagedList對象。 建立PagedList時,會同時發生兩件事:

LiveData将新的PagedList發送到ViewModel,ViewModel又将其傳遞給UI。 UI觀察更改的PagedList并使用其PagedListAdapter更新呈現PagedList資料的RecyclerView。 (在下面的動畫中用空方塊表示)。

PagedList嘗試從DataSource擷取第一個資料塊。 當DataSource為空時,例如,當應用程式第一次啟動且資料庫為空時,它會調用BoundaryCallback.onZeroItemsLoaded()。 在此方法中,BoundaryCallback從網絡請求更多資料并将響應資料插入資料庫中。

android studio 分頁,Android Paging codelab

将資料插入DataSource後,将建立一個新的PagedList對象(由填充的方塊在下面的動畫中表示)。 然後,使用LiveData将此新資料對象傳遞給ViewModel和UI,并在PagedListAdapter的幫助下顯示。

android studio 分頁,Android Paging codelab

當使用者滾動時,PagedList請求DataSource加載更多資料,查詢資料庫以擷取下一個資料塊。 當PagedList分頁來自DataSource的所有可用資料時,會調用BoundaryCallback.onItemAtEndLoaded()。 BoundaryCallback從網絡請求資料并将響應資料插入資料庫中。 然後,基于新加載的資料重新填充UI。

android studio 分頁,Android Paging codelab