Paging library android coroutines

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

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) перед каждым тестом.

Читайте также:  Гайдлайны мобильных платформ ios android

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

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

Источник

Android Paging Library with Kotlin Coroutines

Jan 27, 2019 · 5 min read

Update Note

This article uses a version of the Paging Library which is below v3.0 . Paging 3.0 is a complete re-w r ite of the library. It uses Kotlin, and it has first-class support for Kotlin coroutines and flow . It also has some new features. For example, it supports headers and footers, and it has retry and refresh mechanisms. Luckily, I have a tutorial on raywenderlich.com on the same. You can have a read here: Paging Library for Android With Kotlin: Creating Infinite Lists . You can still continue reading this article in case you are using a version of the paging library which is below v3.0.

Android Paging Library was introduced last year as part of the Android Jetpack libraries for loading infinite lists. Coroutines are a great way to write asynchronous code that is perfectly readable and maintainable. Whereas there are many articles about how to implement the paging library in android, i did not find one explaining how you can leverage the advantage of Kotlin Coroutines to load infinite lists.

This article assumes you have the basic knowledge of Kotlin and Kotlin Coroutines. For more about coroutines, you can check the official documentation.

Getting Started

We will start by configuring the build.gradle file to be as the snippet below shows

After adding our dependencies we are now good to go. We can now add our paging logic to the app. For this app, we are going to use the Reddit API to show how paging library works with coroutines.

Declaring our Retrofit Interface

The following snippet shows our retrofit interface

You may have noticed that instead of Call , we now have a suspend function in our interface function.

Next, we will create our retrofit client class as follows

Notice we have added the CoroutineCallAdpaterFactory() a Retrofit 2 CallAdapter.Factory for Kotlin coroutine’s Deferred .

Choosing and Adding a Data Source

A DataSource is a class that manages the actual loading of data for your list. It provides callbacks that instruct you to load some data at a certain range or with a certain key value. There are three types of DataSource provided by the Paging library:

  • ItemKeyedDataSource
  • PageKeyedDataSource
  • PositionalDataSource

It’s important to choose the data source that best fits your structure. Use:

  • ItemKeyedDataSource if you need to use data from item N to fetch item N+1.
  • PageKeyedDataSource if pages you load embed next/previous keys. For example, if you’re fetching social media posts, you may need to pass a next page key to load the subsequent posts.
  • PositionalDataSource if you need to fetch pages of data from a specific location in your data store.

The Reddit API returns data in such a way that you receive a one-page key that corresponds to a list of Reddit posts. Since you have one key per page of Reddit data, you should use PageKeyedDataSource.This is our PostsDataSource.

Notice instead of the normal retrofit onResponse and onFailure callbacks, we’ve replaced it with scope.launch<> coroutine builder which starts a coroutine in the background and keeps working in the meantime.

In this case, we get the scope in our PostDataSource Class constructor.

Since our fetchPosts function is a suspend function, we can call await on the response and get rid of the retrofit callbacks successfully.

Читайте также:  Assassins creed brotherhood android

When the response is successful, you extract the RedditPost objects from the API response and pass them through to the callback object supplied to you. If you didn’t receive any RedditPosts from the API, simply pass in an empty list. You also pass through both the before and after keys that the Reddit API gave you.

Using a PagedListAdapter

Our adapter extends the PagedListAdapter() class and has two parameters. The first type parameter is RedditPost . That’s the model class this adapter will work with and the class the RedditDataSource produces. The second type parameter is the ViewHolder, just like a normal RecyclerView.Adapter . You also need a DiffUtil class. DiffUtil is a utility class that helps to streamline the process of sending a new list to a RecyclerView.Adapter . It can generate callbacks that communicate with the notifyItemChanged or notifyItemInserted methods of the adapter to update its items in an efficient manner, without you having to deal with that complex logic.

That is the DiffUtilCallBack class with two method: areItemsTheSame and areContentsTheSame. The areItemsTheSame asks whether two items represent the same object. The areContentsTheSame asks whether the content of the same item has changed.

Setting up the View Model

ViewModel will be responsible for creating the paged list along with its configurations. PagedList is a wrapper list that holds your data items and invokes the DataSource to load the elements. It typically consists of a background executor (which fetches the data) and the foreground executor (which updates the UI with the data).

A PageList takes in the following parameters:

a. setEnablePlaceholders (enablePlaceholders :Boolean) — Enabling placeholders to mean there is a placeholder that is visible to the user till the data is fully loaded.
b. setInitialLoadSizeHint (initialLoadSizeHint :Int ) — The number of items to load initially.
c. setPageSize (pageSize:Int) — The number of items to load in the PagedList.
d. setPrefetchDistance (prefetchDistance:Int) — The number of preloads that occur. For instance, if we set this to 10, it will fetch the first 10 pages initially when the screen loads.

Below is our view model with the above configurations

We have the initializedPagedListBuilder which fetches the pagedlist from our data source. In our ViewModel also we pass the viewModelScope to the PostsDataSource factory.

Finally, the wrap-up

This is our activity code after doing all the above steps

As you can see, our activity has little code since as we have placed all the logic in our view model and data source. The activity displays the list of Reddit posts from the ViewModel.

For the full project, you can find it on GitHub

Источник

Android Paging 3 library with Offset and Limit parameters, MVVM, LiveData, and Coroutine

Jun 6 · 4 min read

What is Paging library?

The Paging library helps you load and display pages of data from a larger dataset from local storage or over network. This approach allows your app to use both network bandwidth and system resources more efficiently.

Library Architecture

  • A PagingSource object can load data from any single source, including network sources and local databases.
  • A RemoteMediator object handles paging from a layered data source, such as a network data source with a local database cache.
  • The Pager component provides a public API for constructing instances of PagingData that are exposed in reactive streams, based on a PagingSource object and a PagingConfig configuration object.
  • A PagingData object is a container for a snapshot of paginated data. It queries a PagingSource object and stores the result.
  • The primary Paging library component in the UI layer is PagingDataAdapter , a RecyclerView adapter that handles paginated data.
Читайте также:  Нет звука при входящих смс андроид

What is the CoinRanking application?

C oinRanking is a small demo application based on modern Android application tech-stacks and MVVM architecture. Fetching data from the network and render data via Paging 3, showing crypto ranking.

Prepare to get data from API

local.properties file

  • Add api_key the line also add your API key like
  • Step for development

    After you setup fetch data from network success

    2. Create API service

    I used the endpoint for getting crypto ranking

    and setup like this

    3. Create PagingSource

    PagingSource has two type parameters: Key and Value . The key defines the identifier used to load the data, and the value is the type of the data itself. For example, if you load pages of CryptoItemResponse objects from the network by passing Int page numbers to Retrofit, you would select Int as the Key type and CryptoItemResponse as the Value type.

    The load() function should be implemented to retrieve data from the data source (network in our case). load() is a suspend function, so you can call other suspend functions here like a network call. For more information about suspend functions, check the Kotlin Coroutines docs.

    By default, the initial load size is 3 * page size

    For example, You set NETWORK_PAGE_SIZE = 500

    • First time, PagingSource load 500 x 3 = 1500 items
    • Second time, the next load is 500 items.
    • Third time, the next load is 500 items. etc.

    In code, I sent offset and limit params to API. I calculated offset from the page like

    and sent to API like

    If the endpoint use page, you can send the page to API directly.

    The LoadResult object contains the result of the load operation. LoadResult is a sealed class that takes one of two forms, depending on whether the load() call succeeded:

    • If the load is successful, return the crypto list wrapped in a LoadResult.Page object.
    • If the load is not successful, return the exception wrapped in a LoadResult.Error object.

    The getRefreshKey() is method which takes an PagingState object as a parameter and returns the key to pass into the load() method when the data is refreshed or invalidated after the initial load. The Paging Library calls this method automatically on subsequent refreshes of the data.

    4. Create a repository and pager

    The container for the data returned from PagingSource is called PagingData . A Pager instance has to be created to build a PagingData stream. Three parameters are needed to create an instance. These are:

    • PagingSource is our data source created in the name CryptoListPagingSource .
    • PagingConfig defines how to get data from the PagingSource like page size, prefetch distance, etc. Check official docs for the parameters you can set for PagingSource .
    • An optional initialKey to start loading with a default key.
    • When the Pager object is created, it calls the load() function of the pagingSourceFactory it has.
    • I used LiveData to wrap the result. Other ways you can use Flow , Observable like this

    5. Create ViewModel

    • cachedIn() is used to persist the data beyond configuration changes. The best place to do this in a ViewModel , using the viewModelScope .

    6. Create Adapter

    Normally, RecyclerView use RecyclerView.Adapter or ListAdapter but for Paging 3 we use PagingDataAdapter but behavior like normal adapter nothing special

    7. Binding data to RecyclerView

    The last step is collecting the PagingData object in our fragment or activity and set into the CryptoListAdapter

    You can download the demo at the link below

    Источник

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