Найти тему
Nuances of programming

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

Источник: Nuances of Programming

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

  • Kotlin Coroutines: Для асинхронного программирования.
  • Koin: Для внедрения зависимости.

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

Нормальный случай использования👍
Нормальный случай использования👍

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

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

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

Ошибочный случай использования💩
Ошибочный случай использования💩

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

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

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

-4

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

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

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

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

Retrofit & Coroutines

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

-5

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

-6

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

Paging Library

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

-7

-8

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

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

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

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

-9

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

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

ViewModel

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

-10

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

-11

Koin

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

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

-12

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

val viewModelModule = module {
viewModel { SearchUserViewModel(get(), get()) }
}

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

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

Unit Tests

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

-13
-14

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

Instrumented Tests

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

-15
-16
-17

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

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

Читайте нас в телеграмме и vk

Перевод статьи Philippe BOISNEY: Playing with…