收集分頁資料

本指南以「分頁庫總覽」為基礎,討論如何自訂應用程式的資料載入解決方案,以滿足應用程式的架構需求。

建立可觀察清單

一般而言,您的 UI 程式碼會觀察 LiveData<PagedList> 物件 (如果您使用 RxJava2,則 Flowable<PagedList>Observable<PagedList> 物件),就位於應用程式的 ViewModel 中。此可觀察物件構成應用程式清單資料的呈現方式與內容之間的連結。

如要建立這類可觀測的 PagedList 物件,請傳入 DataSource.Factory 的例項到 LivePagedListBuilderRxPagedListBuilder 物件。DataSource 物件會載入單一 PagedList 的頁面。工廠類別會建立新的 PagedList 例項,以回應內容更新,例如資料庫表無效以及網路重新整理。Room 永久資料庫可以提供 DataSource.Factory 物件,而您也可以自行建立物件

下列程式碼片段說明如何使用 Room 的DataSource.Factory 建構功能在 ViewModel 類別建立新的 LiveData<PagedList> 例項:

ConcertDao

Kotlin

@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>
}

Java

@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

Kotlin

// The Int type argument corresponds to a PositionalDataSource object.
val myConcertDataSource : DataSource.Factory<Int, Concert> =
       concertDao.concertsByDate()

val concertList = myConcertDataSource.toLiveData(pageSize = 50)

Java

// 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>,您也可以定義自己的分頁設定。具體來說,您可以定義下列屬性:

如要進一步控管分頁庫從應用程式資料庫載入清單的時間,請將自訂 Executor 物件傳送至 LivePagedListBuilder,如以下程式碼片段所示:

ConcertViewModel

Kotlin

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
)

Java

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 開始時間的資料來源:

Kotlin

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)
    }
}

Java

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 物件。下列程式碼片段說明如何產生在前幾段程式碼中定義的自訂資料來源的新例項:

Kotlin

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
    }
}

Java

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 信號。然後,您針對該資料來源發出新的例項。下列程式碼片段說明這個行為:

Kotlin

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()
}

Java

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 日期會對應到包含名稱和日期的單一字串:

Kotlin

class ConcertViewModel : ViewModel() {
    val concertDescriptions : LiveData<PagedList<String>>
        init {
            val concerts = database.allConcertsFactory()
                    .map { "${it.name} - ${it.date}" }
                    .toLiveData(pageSize = 50)
        }
}

Java

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,請參閱下列資源。

範例

程式碼研究室

影片