本指南以「分頁庫總覽」為基礎,討論如何自訂應用程式的資料載入解決方案,以滿足應用程式的架構需求。
建立可觀察清單
一般而言,您的 UI 程式碼會觀察 LiveData<PagedList>
物件 (如果您使用 RxJava2,則 Flowable<PagedList>
或 Observable<PagedList>
物件),就位於應用程式的 ViewModel
中。此可觀察物件構成應用程式清單資料的呈現方式與內容之間的連結。
如要建立這類可觀測的 PagedList
物件,請傳入 DataSource.Factory
的例項到 LivePagedListBuilder
或 RxPagedListBuilder
物件。DataSource
物件會載入單一 PagedList
的頁面。工廠類別會建立新的 PagedList
例項,以回應內容更新,例如資料庫表無效以及網路重新整理。Room 永久資料庫可以提供 DataSource.Factory
物件,而您也可以自行建立物件。
下列程式碼片段說明如何使用 Room 的DataSource.Factory
建構功能在 ViewModel
類別建立新的 LiveData<PagedList>
例項:
ConcertDao
@Dao
interface ConcertDao {
// The Int type parameter tells Room to use a PositionalDataSource
// object, with position-based loading under the hood.
@Query("SELECT * FROM concerts ORDER BY date DESC")
fun concertsByDate(): DataSource.Factory<Int, Concert>
}
@Dao
public interface ConcertDao {
// The Integer type parameter tells Room to use a PositionalDataSource
// object, with position-based loading under the hood.
@Query("SELECT * FROM concerts ORDER BY date DESC")
DataSource.Factory<Integer, Concert> concertsByDate();
}
ConcertViewModel
// The Int type argument corresponds to a PositionalDataSource object.
val myConcertDataSource : DataSource.Factory<Int, Concert> =
concertDao.concertsByDate()
val concertList = myConcertDataSource.toLiveData(pageSize = 50)
// The Integer type argument corresponds to a PositionalDataSource object.
DataSource.Factory<Integer, Concert> myConcertDataSource =
concertDao.concertsByDate();
LiveData<PagedList<Concert>> concertList =
LivePagedListBuilder(myConcertDataSource, /* page size */ 50).build();
定義自己的分頁設定
如要進一步設定進階案例的 LiveData<PagedList>
,您也可以定義自己的分頁設定。具體來說,您可以定義下列屬性:
- 「頁面大小」:每頁的項目數。
- 「預先擷取距離」:顯示應用程式使用者介面中最後可見項目數,當這個數大於最少數量時,這分頁資料庫應嘗試事先擷取。這個值應為頁面大小的數倍。
- 「預留位置狀態」:判斷 UI 是否顯示尚未完成載入的清單項目預留位置。如需討論使用預留位置的優缺點,請參閱「在使用者介面中提供預留位置」。
如要進一步控管分頁庫從應用程式資料庫載入清單的時間,請將自訂 Executor
物件傳送至 LivePagedListBuilder
,如以下程式碼片段所示:
ConcertViewModel
val myPagingConfig = Config(
pageSize = 50,
prefetchDistance = 150,
enablePlaceholders = true
)
// The Int type argument corresponds to a PositionalDataSource object.
val myConcertDataSource : DataSource.Factory<Int, Concert> =
concertDao.concertsByDate()
val concertList = myConcertDataSource.toLiveData(
pagingConfig = myPagingConfig,
fetchExecutor = myExecutor
)
PagedList.Config myPagingConfig = new PagedList.Config.Builder()
.setPageSize(50)
.setPrefetchDistance(150)
.setEnablePlaceholders(true)
.build();
// The Integer type argument corresponds to a PositionalDataSource object.
DataSource.Factory<Integer, Concert> myConcertDataSource =
concertDao.concertsByDate();
LiveData<PagedList<Concert>> concertList =
new LivePagedListBuilder<>(myConcertDataSource, myPagingConfig)
.setFetchExecutor(myExecutor)
.build();
選擇正確的資料來源類型
請務必連線至最佳處理資料結構的資料來源:
- 如果頁面載入的是下一個/上一個金鑰,請使用
PageKeyedDataSource
。例如,從網路擷取社群媒體貼文,您可能需要將一次載入的nextPage
符記傳送至後續的載入作業。 - 如果您需要使用項目「N」的資料來擷取項目「N+1」,請使用
ItemKeyedDataSource
。舉例來說,擷取某個討論應用程式的討論串留言,則可能需要傳送最後一個留言的 ID,才能取得下一則留言的內容。 - 如果您需要從資料儲存庫中任何位置擷取資料頁面,請使用
PositionalDataSource
。這個類別支援依據您設定的位置,要求一組資料項目。舉例來說,請求可能會傳回位置為 1500 的 50 個資料項目。
當資料無效時通知我
使用分頁庫時,當資料表或資料列過時時就會通知應用程式的其他層資料層。如要這麼做,請從所選應用程式挑選 DataSource
類別呼叫 invalidate()
。
建立自己的資料來源
如果您使用的是自訂本機資料解決方案,或從網路直接載入資料,您可以實作其中一個 DataSource
子類別。以下程式碼片段顯示特定 concert 開始時間的資料來源:
class ConcertTimeDataSource() :
ItemKeyedDataSource<Date, Concert>() {
override fun getKey(item: Concert) = item.startTime
override fun loadInitial(
params: LoadInitialParams<Date>,
callback: LoadInitialCallback<Concert>) {
val items = fetchItems(params.requestedInitialKey,
params.requestedLoadSize)
callback.onResult(items)
}
override fun loadAfter(
params: LoadParams<Date>,
callback: LoadCallback<Concert>) {
val items = fetchItemsAfter(
date = params.key,
limit = params.requestedLoadSize)
callback.onResult(items)
}
}
public class ConcertTimeDataSource
extends ItemKeyedDataSource<Date, Concert> {
@NonNull
@Override
public Date getKey(@NonNull Concert item) {
return item.getStartTime();
}
@Override
public void loadInitial(@NonNull LoadInitialParams<Date> params,
@NonNull LoadInitialCallback<Concert> callback) {
List<Concert> items =
fetchItems(params.key, params.requestedLoadSize);
callback.onResult(items);
}
@Override
public void loadAfter(@NonNull LoadParams<Date> params,
@NonNull LoadCallback<Concert> callback) {
List<Concert> items =
fetchItemsAfter(params.key, params.requestedLoadSize);
callback.onResult(items);
}
然後,您可以建立 DataSource.Factory
的具體子類別,將這個自訂資料載入 PagedList
物件。下列程式碼片段說明如何產生在前幾段程式碼中定義的自訂資料來源的新例項:
class ConcertTimeDataSourceFactory :
DataSource.Factory<Date, Concert>() {
val sourceLiveData = MutableLiveData<ConcertTimeDataSource>()
var latestSource: ConcertDataSource?
override fun create(): DataSource<Date, Concert> {
latestSource = ConcertTimeDataSource()
sourceLiveData.postValue(latestSource)
return latestSource
}
}
public class ConcertTimeDataSourceFactory
extends DataSource.Factory<Date, Concert> {
private MutableLiveData<ConcertTimeDataSource> sourceLiveData =
new MutableLiveData<>();
private ConcertDataSource latestSource;
@Override
public DataSource<Date, Concert> create() {
latestSource = new ConcertTimeDataSource();
sourceLiveData.postValue(latestSource);
return latestSource;
}
}
考量內容更新的運作方式
當您建構可觀測的 PagedList
物件時,請考慮內容更新的運作方式。如果您是直接從 Room 資料庫載入資料,系統會自動將資料推送到應用程式的使用者介面。
使用分頁網路 API 時,您通常會有使用者互動 (例如「滑動重新整理」) 做為撤消最近最常使用的 DataSource
信號。然後,您針對該資料來源發出新的例項。下列程式碼片段說明這個行為:
class ConcertActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// ...
concertTimeViewModel.refreshState.observe(this, Observer {
// Shows one possible way of triggering a refresh operation.
swipeRefreshLayout.isRefreshing =
it == MyNetworkState.LOADING
})
swipeRefreshLayout.setOnRefreshListener {
concertTimeViewModel.invalidateDataSource()
}
}
}
class ConcertTimeViewModel(firstConcertStartTime: Date) : ViewModel() {
val dataSourceFactory = ConcertTimeDataSourceFactory(firstConcertStartTime)
val concertList: LiveData<PagedList<Concert>> =
dataSourceFactory.toLiveData(
pageSize = 50,
fetchExecutor = myExecutor
)
fun invalidateDataSource() =
dataSourceFactory.sourceLiveData.value?.invalidate()
}
public class ConcertActivity extends AppCompatActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
// ...
viewModel.getRefreshState()
.observe(this, new Observer<NetworkState>() {
// Shows one possible way of triggering a refresh operation.
@Override
public void onChanged(@Nullable MyNetworkState networkState) {
swipeRefreshLayout.isRefreshing =
networkState == MyNetworkState.LOADING;
}
};
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshListener() {
@Override
public void onRefresh() {
viewModel.invalidateDataSource();
}
});
}
}
public class ConcertTimeViewModel extends ViewModel {
private LiveData<PagedList<Concert>> concertList;
private DataSource<Date, Concert> mostRecentDataSource;
public ConcertTimeViewModel(Date firstConcertStartTime) {
ConcertTimeDataSourceFactory dataSourceFactory =
new ConcertTimeDataSourceFactory(firstConcertStartTime);
mostRecentDataSource = dataSourceFactory.create();
concertList = new LivePagedListBuilder<>(dataSourceFactory, 50)
.setFetchExecutor(myExecutor)
.build();
}
public void invalidateDataSource() {
mostRecentDataSource.invalidate();
}
}
提供資料對應
分頁庫支援由 DataSource
載入的項目式和頁面式轉換作業。
在以下程式碼片段中,concert 的名稱和 concert 日期會對應到包含名稱和日期的單一字串:
class ConcertViewModel : ViewModel() {
val concertDescriptions : LiveData<PagedList<String>>
init {
val concerts = database.allConcertsFactory()
.map { "${it.name} - ${it.date}" }
.toLiveData(pageSize = 50)
}
}
public class ConcertViewModel extends ViewModel {
private LiveData<PagedList<String>> concertDescriptions;
public ConcertViewModel(MyDatabase database) {
DataSource.Factory<Integer, Concert> factory =
database.allConcertsFactory().map(concert ->
concert.getName() + "-" + concert.getDate());
concertDescriptions = new LivePagedListBuilder<>(
factory, /* page size */ 50).build();
}
}
如果您想在檔案載入後進行包裝、轉換或準備的項目,就可以採用這個方式。由於這工作是在擷取執行上進行,因此可以採取昂貴的工作,例如從磁碟讀取或查詢獨立的資料庫。
提供意見
歡迎透過下列資源與我們分享意見和想法:
- Issue Tracker
- 報告問題,幫助我們修正錯誤。
其他資源
如要進一步瞭解 Paging Library,請參閱下列資源。
範例
程式碼研究室
影片
目前沒有任何建議。
建議登入 Google 帳戶。