На одном из совещаний отдела Android я услышал один из наш разработчики сделали небольшую библиотеку, которая помогает создавать «бесконечный» список при использовании Realm, сохраняя при этом «ленивую загрузку» и уведомления.
Я составил и написал черновик статьи, которой делюсь с вами практически в неизменном виде.
Со своей стороны пообещал, что разберётся с заданиями и зайдет в комментарии, если возникнут вопросы.
Бесконечный список и готовые решения
Одна из задач, с которыми мы сталкиваемся, — отобразить информацию списком, при пролистывании которого данные загружаются и вставляются незаметно для пользователя.Для пользователя это выглядит так, как будто он пролистывает бесконечный список.
Алгоритм получается примерно следующий:
- получить данные из кэша для первой страницы;
- если кэш пуст, получаем данные сервера, отображаем их в списке и записываем в базу данных;
- если есть кэш, загрузить его в список;
- если мы доходим до конца базы данных, то запрашиваем данные с сервера, отображаем их списком и записываем в базу данных.
Для реализации бесконечной прокрутки можно использовать готовые решения:
- реализацию RecyclerView.OnScrollListener или найдите что-нибудь готовое на Github, например EndlessRecyclerViewScrollListener ;
- реактивный подход использование RxJava для управления загрузкой данных;
- готовая реализация списка ;
- Библиотека подкачки из Jetpack .
Однако при использовании Realm в качестве базы данных мы теряем все, к чему так привыкли — ленивую загрузку и уведомления об изменении данных .
Мы не хотели отказываться от всех этих вещей, но при этом использовать библиотеку Paging.
Возможно, мы не первые, кому это нужно.
Быстрый поиск сразу дал решение — библиотека Монархия королевства .
После беглого изучения выяснилось, что это решение нам не подходит — библиотека не поддерживает ни отложенную загрузку, ни уведомления.
Мне пришлось создать свой собственный.
Итак, требования:
- Продолжайте использовать Realm;
- Сохраните отложенную загрузку для Realm;
- Сохранять уведомления;
- Используйте библиотеку подкачки для загрузки данных из базы данных и постраничной загрузки данных с сервера, как это предлагает библиотека подкачки.
Кратко библиотека состоит из следующих компонентов: Источник данных — базовый класс для постраничной загрузки данных.
У него есть реализации: PageKeyedDataSource, PositionalDataSource и ItemKeyedDataSource, но их назначение нам сейчас не важно.
PagedList — список, загружающий данные порциями из DataSource. Но так как мы используем Realm, загрузка данных порциями для нас не актуальна.
PagedListAdapter — класс, отвечающий за отображение данных, загруженных PagedList. В исходном коде эталонной реализации мы увидим, как работает схема.
1. PagedListAdapter в методе getItem(int index) вызывает метод loadAround(int index) для PagedList:
2. PagedList выполняет проверки и вызывает метод void tryDispatchBoundaryCallbacks(boolean post):/** * Get the item from the current PagedList at the specified index. * <p> * Note that this operates on both loaded items and null padding within the PagedList. * * @param index Index of item to get, must be >= 0, and < {@link #getItemCount()}.
* @return The item, or null, if a null placeholder is at the specified position. */ @SuppressWarnings("WeakerAccess") @Nullable public T getItem(int index) { if (mPagedList == null) { if (mSnapshot == null) { throw new IndexOutOfBoundsException( "Item count is zero, getItem() call is invalid"); } else { return mSnapshot.get(index); } } mPagedList.loadAround(index); return mPagedList.get(index); }
/**
* Load adjacent items to passed index.
*
* @param index Index at which to load.
*/
public void loadAround(int index) {
if (index < 0 || index >= size()) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size());
}
mLastLoad = index + getPositionOffset();
loadAroundInternal(index);
mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index);
mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index);
/*
* mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to
* dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded,
* and accesses happen near the boundaries.
*
* Note: we post here, since RecyclerView may want to add items in response, and this
* call occurs in PagedListAdapter bind.
*/
tryDispatchBoundaryCallbacks(true);
}
3. В этом методе проверяется необходимость загрузки очередной порции данных и делается запрос на скачивание:
/**
* Call this when mLowest/HighestIndexAccessed are changed, or
* mBoundaryCallbackBegin/EndDeferred is set.
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
void tryDispatchBoundaryCallbacks(boolean post) {
final boolean dispatchBegin = mBoundaryCallbackBeginDeferred
&& mLowestIndexAccessed <= mConfig.prefetchDistance;
final boolean dispatchEnd = mBoundaryCallbackEndDeferred
&& mHighestIndexAccessed >= size() - 1 - mConfig.prefetchDistance;
if (!dispatchBegin && !dispatchEnd) {
return;
}
if (dispatchBegin) {
mBoundaryCallbackBeginDeferred = false;
}
if (dispatchEnd) {
mBoundaryCallbackEndDeferred = false;
}
if (post) {
mMainThreadExecutor.execute(new Runnable() {
@Override
public void run() {
dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd);
}
});
} else {
dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd);
}
}
4. В результате все вызовы попадают в DataSource, куда загружаются данные из базы или из других источников:
@SuppressWarnings("WeakerAccess") /* synthetic access */
void dispatchBoundaryCallbacks(boolean begin, boolean end) {
// safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present
if (begin) {
//noinspection ConstantConditions
mBoundaryCallback.onItemAtFrontLoaded(mStorage.getFirstLoadedItem());
}
if (end) {
//noinspection ConstantConditions
mBoundaryCallback.onItemAtEndLoaded(mStorage.getLastLoadedItem());
}
}
Пока всё выглядит просто – бери и делай.
Всего несколько вещей, которые нужно сделать:
- Создайте собственную реализацию PagedList (RealmPagedList), которая будет работать с RealmModel;
- Создайте собственную реализацию PagedStorage (RealmPagedStorage), которая будет работать с OrderedRealmCollection;
- Создайте собственную реализацию DataSource (RealmDataSource), которая будет работать с RealmModel;
- Создайте свой адаптер для работы с RealmList;
- Удалить ненужное, добавить необходимое;
- Готовый.
Попробуем создать приложение, отображающее список пользователей.
0. Добавьте библиотеку в проект: allprojects {
repositories {
maven { url " https://jitpack.io " }
}
}
implementation 'com.github. magora-android:realmpagination:1.0.0 '
1. Создайте класс пользователя: @Serializable
@RealmClass
open class User : RealmModel {
@PrimaryKey
@SerialName("id") var id: Int = 0
@SerialName("login") var login: String? = null
@SerialName("avatar_url") var avatarUrl: String? = null
@SerialName("url") var url: String? = null
@SerialName("html_url") var htmlUrl: String? = null
@SerialName("repos_url") var reposUrl: String? = null
}
2. Создайте источник данных: class UsersListDataSourceFactory(
private val getUsersUseCase: GetUserListUseCase,
private val localStorage: UserDataStorage
) : RealmDataSource.Factory<Int, User>() {
override fun create(): RealmDataSource<Int, User> {
val result = object : RealmPageKeyedDataSource<Int, User>() {
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, User>) {.
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, User>) {
Теги: #разработка Android #realm #библиотека подкачки #ленивая загрузка #бесконечный список
-
Гей Люссак, Жозеф Луи
19 Oct, 24 -
Как Купить Компьютер За 2300 Долларов
19 Oct, 24 -
Микростимулы Для Авторов
19 Oct, 24 -
Интервью В Геймдев-Индустрии
19 Oct, 24