Android studio пагинация recyclerview

Пагинация списков в Android с RxJava. Часть I

Часто при разработке клиента мы сталкиваемся с задачей отображения какой-либо информации с сервера, базы данных или еще чего-нибудь в виде списка. И при прокручивании списка данные должны автоматически подгружаться и вставляться в список незаметно для пользователя. У пользователя вообще должно сложиться впечатление, что он скроллит бесконечный список.

В данной статье я бы хотел рассказать вам о том, как сделать автоподгружаемый список простейшим в реализации для разработчика и максимально эффективным и быстрым для пользователя. А также о том, как нам в этом здорово поможет RxJava с ее главной догмой — «Everything is Stream!»

Как нам реализовать такое в Android?

Для начала определимся с исходными данными:

  1. За отображение списка отвечает компонент RecyclerView (надеюсь, про ListView уже успели все забыть:) ) и все необходимые для настройки RecyclerView классы.
  2. Подгрузка данных будет осуществляться при помощи запросов (в сеть, в БД и т.д.) с классическими для такой задачи параметрами offset и limit

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

  1. Загружаем первую порцию данных для списка. Отображаем эти данные.
  2. При скроллинге списка мы должны отслеживать, какие по номеру элементы отображаются на экране. А конкретно, порядковый номер первого или последнего видимого для пользователя элемента.
  3. При наступлении какого-либо события, например, последний видимый на экране элемент является и последним вообще в списке, мы должны подгружать новую порцию данных. Также необходимо не допустить отправки одинаковых запросов. То есть нужно как-то отписаться от «прослушивания» скроллинга списка.
  4. Новые данные отправить в список. Список необходимо обновить. Снова подписаться на «прослушку» скроллинга.
  5. Пункты 2, 3, 4 необходимо повторять до тех пор, пока уже все данные не будут загружены, ну или при наступлении другого необходимого нам события.

И при чем тут RxJava?

Помните, я в начале говорил про главную догму Rx — «Everything is Stream!». Если в ООП мы мыслим категориями объектов, то в Реактивном — категориями потоков.

Например, взглянем на второй пункт алгоритма. Первое, на чем мы здесь остановимся, это скроллинг и соответственно изменяющийся порядковый номер первого или последнего видимого на экране элемента (в рассматриваемом нами ниже примере — последнего). То есть список при скроллинге постоянно «излучает» номера последнего элемента на протяжении всей своей жизни. Ничего не напоминает? Конечно же, это классический «hot observable». А если быть более конкретным, это PublishSubject . Второе, на роль «слушателя» отлично подойдет Subscriber .

RxJava подобно огромному набору «конструкторов», из которых разработчик может создавать самые различные конструкции. Начало конструкции уже положено — список «выпускает» элементы, являющиеся номерами последней видимой строки списка. Далее можно вставить конструктора (они же по сути и потоки), отвечающие за обработку полученных значений, отправку запросов на подгрузку новых данных и встраивания их в список. Ну обо всем по порядку.

Даешь реактивный код!

А теперь к практической части.

Так как мы создаем новый автоподгружаемый список, то унаследуемся от RecyclerView .

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

Теперь AutoLoadingRecyclerView должен «излучать» порядковый номер последнего видимого на экране элемента.

Однако «излучение» просто порядкового номера не очень удобно в дальнейшем. Ведь это значение нужно обрабатывать. Да и наш канал (он же «излучатель») будет изрядно флудить, что также накладывает проблему на backpressure. Тогда немного усовершенствуем «излучатель». Пусть на выходе мы будем получать сразу уже готовые значения offset и limit , объединенные в следующую модель:

Читайте также:  Какую читалку выбрать для андроида

Уже лучше. А теперь уменьшим «флуд» канала. Пусть канал «излучает» элементы только тогда, когда это необходимо, то есть когда нужно подгрузить новую порцию данных.

Взглянем на код.

Вы, наверное, хотите спросить, откуда я взял вот это условие:

Выявлено оно было чисто эмпирическим путем при предположении, что среднее время запроса — 200-300мс. При данном условии «плавность скроллинга» никак не страдает от параллельной догрузки данных. Если у вас время запроса больше, то можно попробовать либо увеличить limit , либо немного поменять данное условие, чтобы подгрузка данных происходила немного пораньше.

Но все равно полностью от флуда канала мы не избавились. Когда условие начала подгрузки выполняется и скроллинг продолжается, канал продолжает нас «заваливать» сообщениями. И мы имеем все возможности послать несколько раз одинаковые запросы на подгрузку данных, да и backpressure никто не отменял — сетевой клиент может сломаться. Поэтому, как только мы получаем первое сообщение от канала, мы сразу же отписываемся от него, запускаем подгрузку данных, обновляем адаптер и список, и потом снова подписываемся к каналу, который уже не будет «флудить», так как поменяется условие (количество элементов в списке увеличится):

И так по циклу. А теперь внимание на код:

Самое сложное позади. Нам удалось организовать безопасный цикл обновления списка. И все это внутри нашего AutoLoadingRecyclerView .

А для того, чтобы у нас сложилось целостное впечатление, внимание на полный код ниже:

Источник

Урок 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 может выглядеть так:

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

Вариант реализации 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.

Читайте также:  Лучший радар детектор для андроид 2021

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

Источник

Реализация списка с заголовком, футером и пагинацией в Андроид

RecyclerView

RecyclerView — это расширенная версия ListView с некоторыми улучшениями в производительности и с новыми функциями. Как следует из названия, RecyclerView перерабатывает или повторно использует представления элементов при прокрутке. В RecyclerView гораздо проще добавлять анимации по сравнению с ListView. В этом уроке мы разберем, как создать RecyclerView с заголовком, футером, разбиением на страницы и анимацией.

Настройка Gradle

Добавьте следующую зависимость в файл build.gradle:

Добавление RecyclerView в XML представление

После того, как проект будет синхронизирован, добавьте компонент RecyclerView в ваш макет:

Привязка XML с классом JAVA

Теперь в методе onCreate вашей активности добавьте следующий код:

Прежде чем идти дальше, давайте подробно рассмотрим приведенный выше код

  • Layout Manager — Простыми словами, Layout Manager помогает нам определить структуру нашего RecyclerView. Есть три встроенных Layout Managers. Помимо этого, мы можем создать собственный пользовательский Layout Manager, чтобы удовлетворить наши требования.

  1. LinearLayoutManager показывает элементы в списке с вертикальной или горизонтальной прокруткой.
  2. GridLayoutManager показывает элементы в сетке.
  3. StaggeredGridLayoutManager показывает элементы в шахматной сетке.

RecyclerView ItemDecoration

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

В этом примере мы будем использовать ItemDecoration для добавления отступов к каждому элементу.

В вышеприведенном классе мы устанавливаем отступы к нулевому элементу.

RecyclerView Adapter

Теперь давайте настроим адаптер ReeyclerView с заголовком и футером.

Пагинация

Теперь, когда адаптер готов, давайте посмотрим, как добавить пагинацию в список RecyclerView. Это довольно легко сделать и должно быть добавлено в onCreate после установки Adapter to Recycler-View.

Всякий раз, когда данные изменяются в mList, вызывайте функцию ниже, чтобы обновить адаптер RecyclerView и показать новые данные.

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

Источник

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