Paging library android kotlin

Урок 14. Paging Library. Основы

В этом уроке начнем знакомство с Paging Library. Рассмотрим общую схему работы связки PagedList и DataSource.

Полный список уроков курса:

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

Для подключения к проекту добавьте в dependencies

Итак, мы хотим отобразить данные в списке. Данные могут быть откуда угодно: база данных, сервер, файл со строками и т.д. Т.е. любой источник, который может предоставить нам данные для отображения их в списке. Для удобства давайте называть его общим словом Storage.

Обычно мы получаем данные из Storage и помещаем их в List в адаптер. Далее RecyclerView будет у адаптера просить View, а адаптер будет просить данные у List.

Получается такая схема:

RecyclerView >> Adapter >> List

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

С Paging Library схема будет немного сложнее:

RecyclerView >> PagedListAdapter >> PagedList > DataSource > Storage

Т.е. обычный Adapter мы меняем на PagedListAdapter. А вместо List у нас будет связка PagedList + DataSource, которая умеет по мере необходимости подтягивать данные из Storage.

Рассмотрим подробнее эти компоненты.

PagedListAdapter

PagedListAdapter — это RecyclerView.Adapter, заточенный под чтение данных из PagedList.

Как видите, он очень похож на RecyclerView.Adapter. От него также требуется биндить данные в Holder.

1) Ему сразу надо предоставить DiffUtil.Callback. Если вы еще не знакомы с этой штукой, посмотрите мой материал.

2) Нет никакого хранилища данных (List или т.п.)

3) Нет метода getItemCount

Пункты 2 и 3 обусловлены тем, что адаптер внутри себя использует PagedList в качестве источника данных, и он сам будет заниматься хранением данных и определением их количества.

Чтобы передать адаптеру PagedList, мы будем использовать метод адаптера )» target=»_blank» rel=»noopener noreferrer»>submitList.

PagedList

Если не сильно вдаваться в детали, то PagedList — это обертка над List. Он тоже содержит данные и умеет отдавать их методом get(position). Но при этом он проверяет, насколько запрашиваемый элемент близок к концу имеющихся у него данных и при необходимости подгружает себе новые данные с помощью DataSource.

Т.е. у PagedList в списке уже есть, например, 40 элементов. Адаптер просит у него элемент с позицией 31. PagedList дает ему этот элемент и при этом понимает, что адаптер просил элемент, близкий к концу его данных. А значит есть вероятность, что скоро адаптер придет за элементами с позицией 40 и далее. Поэтому PagedList обращается к DataSource за новой порцией данных, например, от 41 до 50.

Создается PagedList с помощью билдера:

От нас требуется предоставить пару Executor-ов. Один для выполнения запроса данных в отдельном потоке, а второй для возврата результатов в UI поток.

На вход конструктору билдера необходимо предоставить DataSource и PagedList.Config. Про DataSource мы поговорим чуть позже, а PagedList.Config — это конфиг PagedList. В нем мы можем задать различные параметры, например, размер страницы.

Создание PagedList.Config может выглядеть так:

Читайте также:  Наушники беспроводные как apple для андроид

Подробно все его параметры мы рассмотрим позже.

Вариант реализации MainThreadExecutor:

DataSource

DataSource — это посредник между PagedList и Storage. Возникает вопрос: зачем нужен этот посредник? Почему PagedList не может напрямую попросить очередную порцию данных у Storage? Потому что у Storage могут быть разные требования к способу запроса данных.

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

Paging Library предоставляет три разных DataSource, которые должны нам помочь связать между собой PagedList и Storage. Это PositionalDataSource, PageKeyedDataSource и ItemKeyedDataSource. В отдельном уроке мы еще подробно рассмотрим, в чем разница между ними. А пока будем работать с PositionalDataSource, т.к. он проще и понятнее остальных.

Практика

Давайте перейдем к практическому примеру и все станет понятнее. В качестве DataSource будем использовать PositionalDataSource.

Итак, чтобы вся схема заработала, нам надо создать DataSource, PagedList и адаптер:

DataSource передаем в PagedList. PagedList передаем в адаптер. Адаптер передаем в RecyclerView.

EmployeeStorage — это созданный мною класс, который эмулирует Storage и содержит 100 Employee записей. Не привожу здесь реализацию этого класса, потому что она не имеет значения. В реальном примере вместо него будет база данных или сервер (Retrofit), к которым мы обращаемся за данными.

MyPositionalDataSource наследует PositionalDataSource и должен реализовать пару методов:

Когда мы создаем PagedList, он сразу запрашивает порцию данных у DataSource. Делает он это методом loadInitial. В качестве параметров он передает нам:
requestedStartPosition — с какой позиции подгружать
requestedLoadSize — размер порции

Используя эти параметры, мы запрашиваем данные у Storage. Полученный результат передаем в callback.onResult

Когда мы прокручиваем список, PagedList подгружает новые данные. Для этого он вызывает метод loadRange. В качестве параметров он передает нам позицию, с которой надо подгружать данные, и размер порции.

Используя эти параметры, мы запрашиваем данные у Storage. Полученный результат передаем в callback.onResult

Я добавил логов в эти методы, чтобы было видно, что происходит.

О том, что означает второй параметр в callback.onResult, поговорим во второй части. А потоки, в которых будет выполняться этот код, обсудим в третьей части.

Для наглядности я сделал гифку, в которой вы можете видеть, какие логи появляются по мере прокрутки списка.

Разбираемся, что происходит.

Сразу после запуска в логах видим строку:

loadInitial, requestedStartPosition = 0, requestedLoadSize = 30

PagedList запросил первоначальную порцию данных размером 30 элементов (requestedLoadSize), начиная с нулевого (requestedStartPosition). DataSource передает эти параметры в Storage, получает данные и возвращает их в PagedList. В итоге адаптер отображает эти записи.

Откуда взялось число 30? По умолчанию размер первоначальной загрузки равен размер страницы * 3. Размер страницы мы установили равным 10 (в PagedList.Config методом setPageSize), поэтому requestedLoadSize равен 30.

Теперь начинаем скроллить список вниз. Когда список показал запись с позицией 20, PagedList запросил следующую порцию данных:

loadRange, startPosition 30, loadSize = 10

Почему он сделал это именно по достижении записи с позицией 20? За это отвечает параметр prefetchDistance. По умолчанию он равен pageSize, т.е. 10. Соответственно, когда до конца списка остается 10 записей, PagedList подгружает следующую порцию.

По мере прокрутки списка, подгружаются следующие порции данных

Читайте также:  Сторонний клиент твиттер андроид

loadRange, startPosition = 40, loadSize = 10
loadRange, startPosition = 50, loadSize = 10
loadRange, startPosition = 60, loadSize = 10
loadRange, startPosition = 70, loadSize = 10
loadRange, startPosition = 80, loadSize = 10
loadRange, startPosition = 90, loadSize = 10
loadRange, startPosition = 100, loadSize = 10

После сотой записи список не прокручивается. Так происходит потому, что мой EmployeeStorage содержит всего 100 записей. При попытке получить у него 10 записей, начиная с позиции 100, он просто вернет пустой список. Когда DataSource передаст этот пустой список в callback.onResult, это будет сигналом для PagedList, что данные закончились. После этого PagedList больше не будет пытаться подгружать данные и список не будет скроллиться.

На этом пока остановимся, иначе будет слишком много новой информации. Рекомендую прочитать урок 2-3 раза, чтобы материал лучше зашел. В следующем уроке продолжим.

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

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

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

Источник

Pagination in Android with Paging 3, Retrofit and kotlin Flow

Haven’t you asked how does Facebook, Instagram, Twitter, Forbes, etc… let you “scroll infinitely” without reach a “end” of information in their apps? Wouldn’t you like to implement something like that?

This “endless scrolling” feature is commonly called “Pagination” and it’s nothing new. In a brief resume, pagination help you to load chunks of data associated with a “page” index.

Let’s assume you have 200 items to display in your RecyclerView. What you would do normally is just execute your request, get the response items and submit your list to your adapter. We already know that RecyclerView by it’s own is optimized for “recycle our views”. But do we really need to get those 200 items immediately? What if our user never reach the top 100, or top 50? The rest of the non displayed items are still keep on memory.

What pagination does (in conjunction with our API) is that it let us establish a page number, and how many items per page can we load. In that way, we can request for the next page of items only when we reach the bottom of our RecyclerView.

Non library approach

Before paging 3, you could have implemented Pagination by adding some listeners to your RecyclerView and those listeners would be triggered when you reach the bottom of your list. There are some good samples about it, here is a video with a vey detailed explanation (it’s on Indi, but it is understandable anyways).

A real PRODUCTION ready example is on the Plaid app. Look at their InfinteScrollListener class.

Android Jetpack Paging 3 and Flow

Today you gonna learn how to implement Pagination by using paging 3 from the android jetpack libraries. For my surprise the codelab of Paging 3 was one of the most easiest I have ever done. Florina Muntenescu did a great job with each step of the codelab, go check it out and give it a try. If you want to go straight to the sample code, check this pull request and see step by step how I implement paging 3 to this project.

Читайте также:  Как пользоваться android control

Источник

Экспериментируем с…

Paging Library, Retrofit, Coroutines, Koin и тестированием

Apr 14, 2019 · 6 min read

Пагинация — достаточно известная, но трудная в реализации функция. Поэтому я решил разработать демонстрационное приложение на основе Android Paging Library с пагинацией, основанной на пользовательских запросах в поиске. Я также использовал Retrofit и следующие библиотеки:

Как работает это приложение?

Разобраться в приложении достаточно просто. Значок “Search” находится на панели инструментов. Нажав на него, пользователь начинает печатать необходимый запрос: при каждом совпадении буквы обновляется RecyclerView , а на API Github запускается новый запрос (если запущен предыдущий, то он отклоняется).

Круговой индикатор процесса находится в нижней части RecyclerView и отображает загрузку следующего запроса.

Пользователь также может использовать фильтр запросов с помощью диалога после нажатия FAB.

Я т акже использовал обработчик ошибок сети. Таким образом, можно уведомить пользователя о случившейся ошибке. В этом приложении происходят два типа ошибок:

  • Pagination error: первый запрос выполнен правильно, а второй дает сбой… В этом случае пользователь получает сообщение и кнопку “ повторить запрос”.
  • Global error: первый запрос дает сбой… В этом случае выходит сообщение и кнопка “обновить” вместо RecyclerView.

Как создать такое приложение?

Процесс создания относительно прост.

Изначально у нас есть фрагмент кода, содержащий RecyclerView, связанный с PagedList с помощью LiveData . PagedList — это список ( List ), который загружает данные по фрагментам (страницам) из DataSource , созданным с помощью DataSource.Factory .

В данном примере UserDataSource не выдает данные напрямую. Эту функцию выполняет UserRepository .

Покажите мне код!

Retrofit & Coroutines

При использовании coroutines с Retrofit каждый вызов должен возвращать ответ Deferred .

Затем в UserRepository нужно вызвать каждый предыдущий запрос с помощью функций suspend с использованием метода .await() :

Функции suspend достаточно читабельны (даже для тех, кто не знаком с coroutines), поэтому легко отгадать, что именно получит с API Github каждая из них.

Paging Library

После подготовки сетевых запросов переходим к настройке классов из Paging Library. Сначала создаем UserDataSource :

Этот класс представляет собой сердце пагинации и наследуется из PageKeyedDataSource .

Метод executeQuery() загружает новый coroutine, который получит данные с API Github. Я также использовал SupervisorJob для облегчения обработки возможных сбоев и отмены действий дочерних элементов.

CoroutineExceptionHandler управляет не перехваченными исключениями (uncaught exceptions).

Затем создаем UserDataSource с помощью UserDataSourceFactory , который наследует DataSource.Factory :

Можно заметить, что объекты CoroutineScope и UserRepository дважды переданы в оба конструктора UserDataSource и UserDataSourceFactory .

Изначально эти объекты были созданы с помощью SearchUserViewModel . Таким образом, при уничтожении VM, можно с легкостью прекратить работу запущенных coroutines.

ViewModel

SearchUserViewModel сконструирует все предыдущие объекты, чтобы создать LiveData из PagedList :

Для наглядности рассмотрим пример с классом BaseViewModel :

Для внедрения зависимости был использован Koin из-за его читаемости и простоты использования.

Рассмотрим пример сетевого модуля Koin для этого приложения:

Выглядит неубедительно? Рассмотрим модуль ViewModel:

Все максимально понятно 👌

Тестирование

Unit Tests

Благодаря Koin и Coroutines модульное тестирование становится более удобным и читабельным:

Для справки: я использовал MockWebServer для имитации HTTP-сервера и пользовательских ответов.

Instrumented Tests

Повторюсь, тестировать с Koin намного проще, особенно при обновлении зависимостей контроллера (Activity/Fragment) перед каждым тестом.

Возможно, вы заметили Thread.sleep(1000) ? RecyclerView исчезает на некоторое время, когда никаких данных не загружено.

Мы рассмотрели использование Android Paging Library с Retrofit и Coroutines, а также несколько способов тестирования.
Весь код проекта доступен в этом репозитории .

Источник

Оцените статью