В этом руководстве, основанном на обзоре библиотеки Paging, рассказывается, как настроить решение для загрузки данных в соответствии с архитектурой приложения.
Создание наблюдаемого списка
Обычно код пользовательского интерфейса отслеживает объект LiveData<PagedList> (или, если вы используете RxJava2, объект Flowable<PagedList> или Observable<PagedList>), который находится в ViewModel приложения. Этот наблюдаемый объект формирует связь между представлением и содержимым данных списка вашего приложения.
Чтобы создать один из этих наблюдаемых объектов PagedList, передайте экземпляр DataSource.Factory объекту LivePagedListBuilder или RxPagedListBuilder. Объект DataSource загружает страницы для одного PagedList. Класс фабрики создает новые экземпляры PagedList в ответ на обновления контента, например недействительность таблицы базы данных и обновления сети. Объекты DataSource.Factory можно получить из библиотеки персистентности Room или создать самостоятельно.
В следующем фрагменте кода показано, как создать новый экземпляр LiveData<PagedList> в классе ViewModel приложения с помощью возможностей создания DataSource.Factory в Room:
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> для более сложных случаев, вы можете задать собственную конфигурацию разбиения на страницы. В частности, вы можете определить следующие атрибуты:
- Размер страницы. Количество элементов на каждой странице.
- Расстояние предварительной выборки. Количество элементов, которые библиотека Paging должна попытаться получить заранее, начиная с последнего видимого элемента в интерфейсе приложения. Это значение должно быть в несколько раз больше размера страницы.
- Наличие плейсхолдеров. Определяет, будут ли в интерфейсе показываться плейсхолдеры для элементов списка, которые ещё не загрузились. О преимуществах и недостатках использования плейсхолдеров рассказывается в статье Как использовать плейсхолдеры в интерфейсе.
Если вы хотите лучше контролировать, когда библиотека Paging загружает список из базы данных приложения, передайте пользовательский объект 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из одной загрузки в последующую. - Используйте
ItemKeyedDataSource, если вам нужно использовать данные из элемента N, чтобы получить элемент N+1. Например, если вы получаете многоуровневые комментарии для приложения для обсуждений, вам может потребоваться передать идентификатор последнего комментария, чтобы получить содержимое следующего. - Используйте
PositionalDataSource, если вам нужно получить страницы данных из любого места в хранилище данных. Этот класс поддерживает запрос набора элементов данных, начиная с любого выбранного вами местоположения. Например, запрос может вернуть 50 элементов данных, начиная с местоположения 1500.
Уведомлять, если данные неверны
При использовании библиотеки Paging за уведомление других уровней приложения об устаревании таблицы или строки отвечает уровень данных. Для этого вызовите метод invalidate() из класса DataSource, который вы выбрали для своего приложения.
Как создавать собственные источники данных
Если вы используете собственное решение для работы с локальными данными или загружаете данные напрямую из сети, вы можете реализовать один из подклассов DataSource. В приведенном ниже фрагменте кода показан источник данных, который привязан к времени начала концерта:
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); }
Затем вы можете загрузить эти настроенные данные в объекты PagedList, создав конкретный подкласс DataSource.Factory. В следующем фрагменте кода показано, как создать новые экземпляры пользовательского источника данных, определенного в предыдущем фрагменте кода:
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(); } }
Сопоставление данных
Библиотека Paging поддерживает преобразования элементов, загруженных с помощью DataSource, на основе элементов и страниц.
В следующем фрагменте кода название и дата концерта сопоставляются с одной строкой, содержащей и название, и дату:
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(); } }
Это может быть полезно, если вы хотите упаковать, преобразовать или подготовить объекты после их загрузки. Поскольку эта работа выполняется в исполнителе выборки, вы можете выполнять потенциально ресурсоемкие задачи, такие как чтение с диска или запрос к отдельной базе данных.
Отправить отзыв
Вы можете поделиться с нами своими отзывами и идеями, используя следующие ресурсы:
- Система отслеживания ошибок

- Сообщайте о проблемах, чтобы мы могли их устранить.
Дополнительные ресурсы
Чтобы узнать больше о библиотеке Paging, ознакомьтесь со следующими ресурсами.
Образцы
Практические работы
Видео
- Android Jetpack: управление бесконечными списками с помощью RecyclerView и Paging (Google I/O, 2018 г.)
- Android Jetpack: Paging
Рекомендуем
- Примечание. Текст ссылки показывается, когда JavaScript отключен.
- Переход на Paging 3
- Обзор библиотеки Paging 2
- Как показывать списки с разбивкой по страницам