本指南以分页库概述为基础,讨论如何自定义应用程序的数据加载解决方案以满足应用程序的体系结构需求。
一、构造一个可观察的列表
通常,您的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
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 Integer type argument corresponds to a PositionalDataSource object.
DataSource.Factory<Integer, Concert> myConcertDataSource =
concertDao.concertsByDate();
LiveData<PagedList<Concert>> concertList =
LivePagedListBuilder(myConcertDataSource, /* page size */ 20).build()
二、定义自己的分页配置
要为高级案例进一步配置LiveData <PagedList>
,您还可以定义自己的分页配置。 特别是,您可以定义以下属性:
-
Page size
:每页中的项目数。 -
Prefetch distance
(预取距离):给定应用程序UI中的最后一个可见项目,超出此分页库应该提前尝试获取的最后一项的项目数。 此值应该是页面大小的几倍。 -
Placeholder presence
(占位符存在):确定UI是否显示尚未完成加载的列表项的占位符。 有关使用占位符的优缺点的讨论,请了解如何在UI中提供占位符。
如果您想更好地控制Paging Library
何时从应用程序的数据库加载列表,请将自定义Executor
对象传递给LivePagedListBuilder
,如以下代码段所示:
ConcertViewModel
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
。 此类支持从您选择的任何位置开始请求一组数据项。 例如,请求可能返回以位置1200开头的20个数据项。
四、数据无效时通知
使用分页库时,数据层由表或行变得陈旧时通知应用程序的其他层。 为此,请从您为应用程序选择的DataSource类中调用invalidate()
。
注意:您的应用的UI可以使用滑动刷新模型触发此数据失效功能。
五、构建自己的数据源
如果使用自定义本地数据解决方案,或者直接从网络加载数据,则可以实现其中一个DataSource
子类。 以下代码段显示了一个数字源,该数据源取决于给定Concert
的开始时间:
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
对象中。 以下代码段显示了如何生成前面代码段中定义的自定义数据源的新实例:
public class ConcertTimeDataSourceFactory
extends DataSource.Factory<Date, Concert> {
private MutableLiveData<ConcertTimeDataSource> mSourceLiveData =
new MutableLiveData<>();
@Override
public DataSource<Date, Concert> create() {
ConcertTimeDataSource source =
new ConcertTimeDataSource();
mSourceLiveData.postValue(source);
return source;
}
}
六、考虑内容更新的工作原理
在构造可观察的PagedList
对象时,请考虑内容更新的工作方式。 如果您直接从Room数据库加载数据,则会自动将更新推送到您应用的UI。
如果您使用的是分页网络API,则通常会进行用户交互,例如“滑动以刷新”,作为使当前数据源无效并请求新数据源的信号。 此行为显示在以下代码段中:
public class ConcertActivity extends AppCompatActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
// ...
mViewModel.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() {
mViewModel.invalidateDataSource();
}
});
}
}
public class ConcertTimeViewModel extends ViewModel {
private LiveData<PagedList<Concert>> mConcertList;
private DataSource<Date, Concert> mConcertTimeDataSource;
public ConcertTimeViewModel(Date firstConcertStartTime) {
ConcertTimeDataSourceFactory dataSourceFactory =
new ConcertTimeDataSourceFactory(firstConcertStartTime);
mConcertTimeDataSource = dataSourceFactory.create();
mConcertList = new LivePagedListBuilder<>(dataSourceFactory, 20)
.setFetchExecutor(myExecutor)
.build();
}
public void invalidateDataSource() {
mConcertTimeDataSource.invalidate();
}
}
七、提供数据表示之间的映射
分页库支持由DataSource加载的项目的基于项目和基于页面的转换。
在以下代码段中,Concert名称和Concert日期的组合映射到包含名称和日期的单个字符串:
public class ConcertViewModel extends ViewModel {
private LiveData<PagedList<String>> mConcertDescriptions;
public ConcertViewModel(MyDatabase database) {
DataSource.Factory<Integer, Concert> factory =
database.allConcertsFactory().map(concert ->
concert.getName() + "-" + concert.getDate());
mConcertDescriptions = new LivePagedListBuilder<>(
factory, /* page size */ 30).build();
}
}
如果要在加载项目后对其进行换行,转换或准备,这可能很有用。 由于此工作是在提取执行程序上完成的,因此您可以执行可能昂贵的工作,例如从磁盘读取或查询单独的数据库。
注意:作为map()的一部分重新查询,JOIN查询总是更有效。