Сбор данных со страниц

В этом руководстве, основанном на обзоре библиотеки 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 загружает список из базы данных приложения, передайте пользовательский объект 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, ознакомьтесь со следующими ресурсами.

Образцы

Практические работы

Видео