- Pagination in Android with Paging 3, Retrofit and kotlin Flow
- Non library approach
- Android Jetpack Paging 3 and Flow
- Как натянуть сыр на страницу, или MVI Paging3 в Android
- Источник данных
- Слой модели
- Отображение данных
- Что дальше
- Paging 3 Android Tutorial
- Introduction to Paging 3 library
- Understanding and Implementing Paging 3 library
- Step 01.
- Step 02.
- Step 03.
- Step 04.
- Step 05.
- Step 06.
- Step 07.
- Getting the States of the data
- Adding the Header and Footer view
- Using it with RxJava
- Step 01.
- Step 02.
- Step 03.
- Step 04.
- Conclusion
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.
Источник
Как натянуть сыр на страницу, или MVI Paging3 в Android
Привет! Меня зовут Георгий Бердников. Я разработчик в компании 65apps, занимаюсь созданием мобильных приложений на Android. Сегодня расскажу о том, как совместить приятное с полезным, поймать двух зайцев и журавля с неба инженеру, перед которым встала сложная задача реализовать постраничную загрузку в приложении.
Библиотека Paging3 упрощает работу с пагинацией. Она всё делает сама: вам не нужно заниматься ручной передачей данных по заветам популярных архитектур, таких как MVI, MVVM и MVP. Снаружи задумка кажется хорошей, но она может стать ложкой дёгтя в бочке мёда. Инструменты, взаимодействующие с общим состоянием (к таким относятся, например, средства отладки в фреймворке MVIKotlin), не смогут контактировать с Paging3.
В статье я покажу, как решить эту проблему. В качестве плацдарма для модификаций был выбран небезызвестный сэмпл от Google, в который мы и внедрим подставьте сюда фреймворк своей мечты (в статье используется вышеупомянутый MVIKotlin). Наш взор падёт только на Paging3, функциональность вставки и удаления предметов оставим в стороне.
Источник данных
Всё, что связано с получением данных — Entity, DAO, база данных — останется неизменным. Эта статья будет вкусной! 🧀🧀🧀
Слой модели
В первую очередь назначим ответственного за получение данных. Это будет CheeseSource:
Nullable-элементы в списке — способ реализации плейсхолдеров, аналогичный тому, что AsyncPagingDataDiffer.getItem() может вернуть null.
Затем создадим способ передачи данных между компонентами. Встречайте — CheeseStore:
Intent один, и его название говорит само за себя: он отвечает за загрузку элементов при скроллинге на определенный индекс (о скроллинге чуть позже). Intent и State, проще говоря, являются входом и выходом метода CheeseSource.get(), вокруг которого вертится весь слой.
Совместить все входы и выходы и понять смысл жизни поможет CheeseExecutor:
Как можно заметить, он не только отвечает на намерения, но и отдаёт первую порцию данных в начале работы Store.
Настало время для самой важной части статьи: реализация CheeseSource!
CheeseDatabaseSource постоянно отслеживает Pager.flow и передаёт PagingData из него в dataDiffer, используемый нами вовсе не для измерения разницы между списками, а для получения заветных данных!
noopListUpdateCallback нужен лишь для создания dataDiffer.
Сакральный метод get() разделяется на две части, одна из которых отвечает за первичную загрузку данных, а вторая — за последующие. Если элементов в dataDiffer не оказалось, мы ждём их появления. Если они есть — передаем новый индекс и ждём, пока закончится загрузка.
ItemSnapshotList из Paging3 реализует интерфейс List , поэтому мы вольны без каких-либо манипуляций вернуть snapshot().
Наконец, соберём всё в CheeseStoreFactory:
Отображение данных
В первую очередь, создадим CheeseView:
UI-часть сэмпла придется немного поменять. Нас, по вышеописанным причинам, не устраивает CheeseAdapter, который наследуется от PagingDataAdapter. Однако вполне устраивает уже написанный CheeseViewHolder, поэтому мы будем управлять списком сами, сделав RecyclerView.Adapter родителем CheeseAdapter и используя AsyncListDiffer:
Вы можете использовать любые удобные инструменты работы со списками, например, AdapterDelegates, но во имя простоты в этой статье используется минимум библиотек.
Поскольку нам самим необходимо уведомлять слой данных о том, что мы хотим отобразить новый контент, создадим OnScrollListener, уведомляющий о каждом новом элементе в области видимости пользователя:
Дело за малым — использовать всё в реализации CheeseView:
Череда изменений завершена. Итоговое состояние проекта можно посмотреть здесь. Он смог совместить в себе MVI, широко используемый в новейших мобильных приложениях, и Paging3 с великими возможностями, а значит ничто теперь не остановит вас на пути к молниеносному созданию производительных приложений со списками!
Что дальше
Paging3 обширна, и не все её возможности взаимодействия с MVI могли быть рассмотрены в этой статье. Если найдёте другие особенности интеграции — указывайте в комментариях или пишите мне в телеграм @berdorge.
Еще одна задача на будущее, помимо внедрения — оптимизировать быстродействие. Ну и, если тема вызовет интерес, можно подумать над тем, чтобы оформить все в виде отдельной библиотеки.
ПС. остался самый главный вопрос: какой сыр мне выбрать, чтобы отпраздновать столь яркую победу? 🙂
Источник
Paging 3 Android Tutorial
When working with RecyclerViews in our Android project, we display a list with a lot of items in it. Sometimes we have use-case like fetching a contact list from the phone and displaying it on the list. Loading a lot of data at once in a list is not a very efficient task to do. To get over this, we have paging in Android.
By using the Paging library, we can load a lot of data efficiently, and only we will load the data when needed. In this blog, we will be learning about the Paging 3 library in Android and how we can use it to load our large set of data in our RecyclerView efficiently.
In this blog, we are going to learn,
- Introduction to Paging 3 library
- Understanding and Implementing Paging 3 library.
- Getting the States of the data
- Adding the Header and Footer view.
- Using it with RxJava.
Introduction to Paging 3 library
Google launched Paging 3 as a part of the Jetpack series. It is still in the early stages. Paging 3 is written entirely using Kotlin Coroutines. It has support for requesting the next page to load more data automatically. It supports Flow, LiveData, and RxJava along with Kotlin Coroutine.
Paging 3 also adds support for caching and handles the state efficiently like loading and failed states while loading the items on ti the list. It also keeps track for the keys to get data from the next and previous pages.
Understanding and Implementing Paging 3 library
In this, we are going to learn implementing Paging 3 library by fetching the list from an API and displaying it in the list. We are going to use the following API for displaying the list,
Our project structure looks like,
Here, we have an adapter package which is responsible for displaying the list in the RecyclerView. data package is responsible for fetching data from the API. We also have MainActivity in which we are going to display the list.
So, now let’s get started.
Step 01.
Let’s first setup the dependencies for Paging 3 in our app’s build.gradle like,
Note: At the time of writing the blog, its version is 3.0.0-alpha04
and we will also add the support for Retrofit and Moshi as we are going to use Moshi as convertor factory for Retrofit like,
Step 02.
Now, we are going to setup APIService and Retrofit client like,
Here, we have created an APIService interface with getListData function which takes a query parameter which will take the page number from which we have to fetch the data from.
We also setup the Retrofit client, by adding the base URL and the convertor factory of Moshi.
Step 03.
Now, we have to create the data class for the following JSON output which we get as the response,
and we create the corresponding data classes for the above JSON response. Our ApiResponse data class looks like,
The Ad data class looks like,
and Data data class looks like,
Step 04.
Now, since we have setup the data class and APIService, let’s setup the paging library. In PostDataSource we will update the code as,
Here, we have extended PostDataSource with PagingSource which will implement a suspend load function which will help us to load the data.
PostDataSource also takes a primary constructor parameter APIService. PostDataSource acts here as a repository and the load function gets the data from the API.
Since the load function is a suspend function, we can call other suspend functions inside it without any issues which we created in APIService.
In the PostDataSource, we take two parameters one of integer type and other of the data type we have to load on the list item. The integer parameter represents the page number here.
Now, we update the load function like,
Here, we get the page number from params and assign it to nextPage variable using param.key and if it returns null, we set a default value 1.
We also do the API call and get assign the response to the response variable using APIService which we passed as a constructor parameter to PostDataSource class.
After doing all the operations with the successful response, we return the LoadResult.Page object here with the required data and it something went wrong we use LoadResult.Error.
We are also passing null as the next key if there is no corresponding data in that direction.
Here, the PostDataSource is responsible to keep track of the page which has to be requested next which we get from the APIService.
Step 05.
Now, since we have the DataSource setup we will now get the data in the MainViewModel. We will create the MainViewModel by adding the APIService as a parameter to the primary constructor. So, we will update the ViewModel like,
Now, inside the ViewModel, we will create a variable called listData and assign it to the Pager instance.
Here, the Pager will call the load function from the PostDataSource and we will pass the apiService as well in the object of PostDataSource.
In the Pager object, we also pass the configuration which is used to configure the behavior of Paging and how it should load its data. We are passing the PagingConfig with the pageSize.
PageSize specifics the number of items loaded at once from the PostDataSource. Recommended is to always keep the page size to more than the visible items on the screen.
And lastly, we will convert the listData as flow by adding .flow .
It will convert the stream of data into a Flow. If we want to return LiveData we can replace .flow with .liveData.
And at last, we cache the data in viewModelScope, and the data will be alive until the scope is active. Here, we are using viewModelScope and since we are caching the data in the ViewModel it would not be impacted on any configuration changes.
Step 06.
Now, since we are getting the data in ViewModel, we now need to pass the data in the RecyclerView of MainActivity. We will update the activity_main.xml like,
Now, we will update the MainActivity like,
Here, we have setup our ViewModel by passing the MainViewModelFactory with the instance of APIService.
The MainViewModelFactory looks like,
And then we have to setup the RecyclerView in the setupList function and lastly, in the setupView function, we will collect the data from listData variable from ViewModel inside the lifecycleScope and then pass it to the submitData function which is part of PagingDataAdapter which helps us to update the data we fetched from the API and display in the UI.
Step 07.
Now, we will update the MainListAdapter. We will extend the MainListAdapter with PagingDataAdapter and the PagingDataAdapter will take the type of data we need to display in the list and the ViewHolder.
It also takes a DiffUtil callback, as a parameter to its primary constructor which helps the PagingDataAdapter to update the items if they are changed or updated. And DiffUtil callback is used because they are more performant.
Now, the MainListAdapter looks like,
Here, in the MainListAdapter, inside the onCreateViewHolder we are returning the ViewHolder by inflating the R.layout.list_item.
Here, the list_item layout file looks like,
and inside the onBindViewHolder we will assign the full name and email to both the TextViews.
And when we run the app, we will see the desired output on the screen and when we scroll the list to the bottom it will load the item from the second page and so on.
Getting the States of the data
Now, while loading the data we might also want to show progress view, and when we have loaded the data we should hide the progress view.
To react to the states of data we use addLoadStateListener like,
Here, we get the CombinedLoadState in addLoadStateListener and we use this to show or hide the progress views.
We can also react to errors inside the addLoadStateListener if all the state methods like refresh, append, and prepend are instance of LoadState.Error.
Adding the Header and Footer view
Now, since we have successfully integrated the Paging 3 library, now we will see how we can setup a footer view and header view.
FooterView is the one that when we go the last item of the list and the data is getting loaded from the next page, the footer view gets displayed. And as soon as the data is loaded the footer view is hidden.
For creating these views we will create a RecyclerViewAdapter similar to how we do in general use of RecyclerViews.
But in this case, we will extend the RecyclerView with LoadStateAdapter and pass the ViewHolder to it like,
Here, inside the onBindViewHolder, we get an additional parameter now of LoadState which we can use to show or hide the loading view based on if the data is getting loaded or not and add it to the adapter we use withLoadStateHeaderAndFooter like,
If we want to just add a footer we will use,
and to use with header we use,
And now, if the data is getting loaded from the paging library, the footer/header is displayed based on which one or both are attached to the RecyclerView adapter.
Using it with RxJava
If we don’t want to use it with Coroutines and Flow and want to go ahead and use RxJava, we still need to follow the above steps but with few changes.
Let me list them down.
Step 01.
First, we need to add the dependencies for Paging 3 Rx support,
and we will also add the dependency for RxJava2 adapter factory,
Step 02.
Now, as a next step, we need to update the PostDataSource and extend it using RxPagingSource in place of PagingSource like,
Now, the PostDataSource will implement the loadSingle function of return type Single like,
Step 03.
Now, we will update the APIService interface like,
Here, the getListData returns a Single and is not a suspend function anymore. And we have added another adapter factory to support RxJava2 to the Retrofit builder.
Step 04.
Now, inside the loadSingle function, we can do our tasks and return LoadResult.Page object if the output is successful or LoadResult.Error if it isn’t.
And to get the data from the PostDataSource to ViewModel we will update the listData variable in MainViewModel like,
This will return a Flowable of PagingData and their rest remains the same as above.
We can also pass make listData of type Observable of PagingData like,
The MainListAdapter remains untouched and in the setupView function in MainActivity gets updated like,
Here, we are passing the lifecycle of the View and the paging data to submit data to inflate the list.
This is how you can use the Paging 3 library with RxJava as well.
Conclusion
Paging 3 library is still in its early stage and it is completely re-written using Kotlin coroutines but it can be used with Flow and RxJava both.
Источник