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

Это руководство основано на обзоре библиотеки подкачки и описывает, как можно настроить решение для загрузки данных вашего приложения в соответствии с потребностями архитектуры вашего приложения.

Создайте наблюдаемый список

Обычно ваш код пользовательского интерфейса наблюдает за объектом LiveData<PagedList> (или, если вы используете RxJava2 , объектом Flowable<PagedList> или Observable<PagedList> ), который находится во ViewModel вашего приложения. Этот наблюдаемый объект формирует связь между представлением и содержимым данных списка вашего приложения.

Чтобы создать один из этих наблюдаемых объектов PagedList , передайте экземпляр DataSource.Factory объекту LivePagedListBuilder или RxPagedListBuilder . Объект DataSource загружает страницы для одного PagedList . Фабричный класс создает новые экземпляры PagedList в ответ на обновления содержимого, такие как аннулирование таблицы базы данных и обновление сети. Библиотека персистентности Room может предоставить вам объекты DataSource.Factory , или вы можете создать свой собственный .

В следующем фрагменте кода показано, как создать новый экземпляр LiveData<PagedList> в классе ViewModel вашего приложения, используя возможности построения Room DataSource.Factory :

КонцертДао

Котлин

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

КонцертViewModel

Котлин

// 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> для сложных случаев, вы также можете определить собственную конфигурацию подкачки. В частности, вы можете определить следующие атрибуты:

  • Размер страницы : количество элементов на каждой странице.
  • Расстояние предварительной выборки : учитывая последний видимый элемент в пользовательском интерфейсе приложения, количество элементов за пределами этого последнего элемента, которые библиотека подкачки должна попытаться получить заранее. Это значение должно быть в несколько раз больше размера страницы.
  • Наличие заполнителя : определяет, отображает ли пользовательский интерфейс заполнители для элементов списка, загрузка которых еще не завершена. Для обсуждения преимуществ и недостатков использования заполнителей узнайте, как добавить заполнители в свой пользовательский интерфейс .

Если вам нужен больший контроль над тем, когда библиотека подкачки загружает список из базы данных вашего приложения, передайте пользовательский объект Executor в LivePagedListBuilder , как показано в следующем фрагменте кода:

КонцертViewModel

Котлин

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 из одной загрузки в последующую загрузку.
  • Используйте ItemKeyedDataSource , если вам нужно использовать данные из элемента N для получения элемента N+1 . Например, если вы получаете цепочки комментариев для приложения обсуждения, вам может потребоваться передать идентификатор последнего комментария, чтобы получить содержимое следующего комментария.
  • Используйте PositionalDataSource , если вам нужно получить страницы данных из любого места, которое вы выберете в своем хранилище данных. Этот класс поддерживает запрос набора элементов данных, начиная с любого выбранного вами местоположения. Например, запрос может вернуть 50 элементов данных, начиная с местоположения 1500.

Уведомлять, когда данные недействительны

При использовании библиотеки подкачки уровень данных должен уведомлять другие уровни вашего приложения, когда таблица или строка устарели. Для этого вызовите invalidate() из класса DataSource , который вы выбрали для своего приложения.

Создайте свои собственные источники данных

Если вы используете собственное решение для локальных данных или загружаете данные непосредственно из сети, вы можете реализовать один из подклассов DataSource . В следующем фрагменте кода показан источник данных, привязанный к времени начала данного концерта:

Котлин

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

Затем вы можете загрузить эти настроенные данные в объекты PagedList , создав конкретный подкласс DataSource.Factory . В следующем фрагменте кода показано, как создавать новые экземпляры пользовательского источника данных, определенного в предыдущем фрагменте кода:

Котлин

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

При использовании API страничной сети обычно взаимодействие с пользователем, например «проведите пальцем для обновления», служит сигналом для аннулирования DataSource , который вы использовали последним. Затем вы запрашиваете новый экземпляр этого источника данных. Следующий фрагмент кода демонстрирует такое поведение:

Котлин

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

Обеспечить сопоставление данных

Библиотека подкачки поддерживает преобразования элементов на основе элементов и страниц, загруженных DataSource .

В следующем фрагменте кода комбинация названия концерта и даты концерта отображается в одну строку, содержащую как название, так и дату:

Котлин

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

Это может быть полезно, если вы хотите обернуть, преобразовать или подготовить элементы после их загрузки. Поскольку эта работа выполняется исполнителем выборки, вы можете выполнять потенциально дорогостоящую работу, например чтение с диска или запрос к отдельной базе данных.

Оставьте отзыв

Поделитесь с нами своими отзывами и идеями через эти ресурсы:

Трекер проблем
Сообщайте о проблемах, чтобы мы могли исправить ошибки.

Дополнительные ресурсы

Чтобы узнать больше о библиотеке подкачки, обратитесь к следующим ресурсам.

Образцы

Кодлабы

Видео

{% дословно %} {% дословно %} {% дословно %} {% дословно %}