收集分页数据

本指南在 Paging 库概览的基础上更进一步,探讨如何自定义应用的数据加载解决方案以满足应用的架构需求。

构造可观察列表

通常,您的界面代码会观察 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

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

KotlinJava
// 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>,您还可以定义自己的分页配置。具体而言,您可以定义以下属性:

  • 页面大小:每个页面中的项数。
  • 预取距离:给定应用界面中的最后一个可见项,Paging 库应尝试提前获取的超出此最后一项的项数。此值应是页面大小的数倍大。
  • 占位符存在:确定界面是否对尚未完成加载的列表项显示占位符。如需查看有关使用占位符的优缺点的探讨,请参阅如何在界面中提供占位符

如果您希望进一步掌控 Paging 库何时从应用的数据库中加载列表,请将自定义 Executor 对象传递给 LivePagedListBuilder,如以下代码段所示:

ConcertViewModel

KotlinJava
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 个数据项。

数据无效时发送通知

使用 Paging 库时,数据层负责在表或行过时的时候通知应用的其他层。为此,请从您为应用选择的 DataSource 类中调用 invalidate()

构建您自己的数据源

如果您使用自定义本地数据解决方案,或直接从网络加载数据,则可以实现其中一个 DataSource 子类。以下代码段展示了从指定音乐会开始时间开始的数据源:

KotlinJava
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 对象中。以下代码段展示了如何生成前面代码段中定义的自定义数据源的新实例:

KotlinJava
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 的失效。然后请求该数据源的新实例。以下代码段演示了此行为:

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

}

提供数据映射

Paging 库支持基于项或基于页面转换由 DataSource 加载的项。

在以下代码段中,音乐会名称和音乐会日期的组合映射到同时包含名称和日期的单个字符串:

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

如果您希望在项加载后进行换行、转换或准备,这将非常有用。由于这项工作是在提取执行程序上完成的,因此您可以执行开销可能很大的工作,如从磁盘读取或查询单独的数据库。

提供反馈

通过以下资源与我们分享您的反馈和想法:

问题跟踪器
报告问题,以便我们修复 bug。

其他资源

如需详细了解 Paging 库,请参阅以下资源。

示例

Codelab

视频

目前没有任何推荐文档页面。

请尝试您的 Google 账号。