(译) 收集分页数据

原文链接

本指南以分页库概述为基础,讨论如何自定义应用程序的数据加载解决方案以满足应用程序的架构需求。

构建可观察的列表

通常,你的 UI 代码会观察 LiveData 对象(或者,如果你使用的是RxJava2,则为 Flowable 或 Observable 对象),该对象位于应用程序的ViewModel 中。此可观察对象在应用程序列表数据的表示和内容之间形成连接。

为了创建这些可观察的 PagedList 对象之一,将 DataSource.Factory 的实例传递给LivePagedListBuilder 或 RxPagedListBuilder 对象。 DataSource 对象加载单个PagedList 的页面。工厂类创建 PagedList 的新实例以响应内容更新,例如数据库表失效和网络刷新。 Room 持久化库可以为你提供 DataSource.Factory 对象,或者你可以构建自己的对象。

以下代码段显示了如何使用 Room 的 DataSource.Factory 构建功能在应用程序的ViewModel 类中创建 LiveData 的新实例:

ConvertDao

1
2
3
4
5
6
7
@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

1
2
3
4
5
6
// 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,你还可以定义自己的分页配置。特别是,你可以定义以下属性:

  • 页面大小:每页中的项目数。
  • 预取距离:给定应用程序 UI 中的最后一个可见项目,这是分页库应该提前尝试提取的最后一项之外的项目数。此值应该是页面大小的几倍。
  • 占位符存在:确定 UI 是否显示尚未完成加载的列表项的占位符。有关使用占位符的优缺点的讨论,请了解如何在 UI 中提供占位符。

如果你想更好地控制分页库何时从应用程序的数据库加载列表,请将自定义 Executor 对象传递给 LivePagedListBuilder,如以下代码段所示:

ConcertViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()。

注意:你的应用的 UI 可以使用滑动刷新模型触发此数据失效功能。

构建你自己的数据源

如果使用自定义本地数据解决方案,或者直接从网络加载数据,则可以实现其中一个DataSource子类。以下代码段显示了一个数字源,该数据源与给定Concert的开始时间相关联:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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对象中。以下代码段显示了如何生成前面代码段中定义的自定义数据源的新实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 数据库加载数据,则会自动将更新推送到你应用的UI。

使用分页网络 API 时,你通常会进行用户交互,例如“滑动以刷新”,作为使您最近使用的数据源无效的信号。然后,你请求该数据源的新实例。以下代码段演示了此行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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 日期的组合映射到包含名称和日期的单个字符串:

1
2
3
4
5
6
7
8
9
10
11
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();
}
}

如果要在项目加载后进行换行,转换或准备,这可能很有用。由于此工作是在提取执行程序上完成的,因此你可以执行可能昂贵的工作,例如从磁盘读取或查询单独的数据库。

非典型前端coder wechat
想要随时Follow我的最新博客,可扫码关注我的公众号