Использование Библиотеки Подкачки С Realm

На одном из совещаний отдела Android я услышал один из наш разработчики сделали небольшую библиотеку, которая помогает создавать «бесконечный» список при использовании Realm, сохраняя при этом «ленивую загрузку» и уведомления.

Я составил и написал черновик статьи, которой делюсь с вами практически в неизменном виде.

Со своей стороны пообещал, что разберётся с заданиями и зайдет в комментарии, если возникнут вопросы.



Бесконечный список и готовые решения

Одна из задач, с которыми мы сталкиваемся, — отобразить информацию списком, при пролистывании которого данные загружаются и вставляются незаметно для пользователя.

Для пользователя это выглядит так, как будто он пролистывает бесконечный список.

Алгоритм получается примерно следующий:

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

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

Для реализации бесконечной прокрутки можно использовать готовые решения:

Используем в качестве мобильной базы данных Область , и опробовав все перечисленные подходы, мы остановились на использовании библиотеки Paging. На первый взгляд, Android Paging Library является отличным решением для загрузки данных и при использовании sqlite совместно с Room отлично подходит в качестве базы данных.

Однако при использовании Realm в качестве базы данных мы теряем все, к чему так привыкли — ленивую загрузку и уведомления об изменении данных .

Мы не хотели отказываться от всех этих вещей, но при этом использовать библиотеку Paging.

Возможно, мы не первые, кому это нужно.

Быстрый поиск сразу дал решение — библиотека Монархия королевства .

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

Мне пришлось создать свой собственный.

Итак, требования:

  1. Продолжайте использовать Realm;
  2. Сохраните отложенную загрузку для Realm;
  3. Сохранять уведомления;
  4. Используйте библиотеку подкачки для загрузки данных из базы данных и постраничной загрузки данных с сервера, как это предлагает библиотека подкачки.

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

Кратко библиотека состоит из следующих компонентов: Источник данных — базовый класс для постраничной загрузки данных.

У него есть реализации: PageKeyedDataSource, PositionalDataSource и ItemKeyedDataSource, но их назначение нам сейчас не важно.

PagedList — список, загружающий данные порциями из DataSource. Но так как мы используем Realm, загрузка данных порциями для нас не актуальна.

PagedListAdapter — класс, отвечающий за отображение данных, загруженных PagedList. В исходном коде эталонной реализации мы увидим, как работает схема.

1. PagedListAdapter в методе getItem(int index) вызывает метод loadAround(int index) для PagedList:

  
  
  
  
  
  
   

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

2. PagedList выполняет проверки и вызывает метод void tryDispatchBoundaryCallbacks(boolean post):

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

Пока всё выглядит просто – бери и делай.

Всего несколько вещей, которые нужно сделать:

  1. Создайте собственную реализацию PagedList (RealmPagedList), которая будет работать с RealmModel;
  2. Создайте собственную реализацию PagedStorage (RealmPagedStorage), которая будет работать с OrderedRealmCollection;
  3. Создайте собственную реализацию DataSource (RealmDataSource), которая будет работать с RealmModel;
  4. Создайте свой адаптер для работы с RealmList;
  5. Удалить ненужное, добавить необходимое;
  6. Готовый.

Опустим мелкие технические подробности и вот результат — Библиотека RealmPagination .

Попробуем создать приложение, отображающее список пользователей.

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 #библиотека подкачки #ленивая загрузка #бесконечный список

Вместе с данным постом часто просматривают: