Desk of Contents
We’ll use Retrofit & Hilt on this article, so it’s higher you know the way they work.
Additionally, we’ll use this API for testing. I like to recommend you register and get your API key.
def paging_version = "3.1.1"
implementation "androidx.paging:paging-runtime:$paging_version"
implementation "androidx.paging:paging-compose:1.0.0-alpha17"//Different Dependencies
def retrofit_version = "2.9.0"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
def hilt_version = "2.44"
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"
implementation "androidx.hilt:hilt-navigation-compose:1.0.0"
Don’t overlook so as to add Web permission in AndroidManifest.xml
,
<uses-permission android:title="android.permission.INTERNET" />
Earlier than we setup Retrofit, let’s see the response of the endpoint that we’ll use. Endpoint, https://newsapi.org/v2/every little thing?q=apple&sortBy=recognition&apiKey=APIKEY&pageSize=20&web page=1
{
"standing": "okay",
"totalResults": 65739,
"articles": [
{
"source": {
"id": "wired",
"name": "Wired"
},
"author": "Parker Hall",
"title": "Apple Music Sing Adds 'Karaoke Mode' to Streaming Songs",
"description": "America's most popular music streaming service is adding the ability to turn down the vocals and sing along.",
"url": "https://www.wired.com/story/apple-music-sing/",
"urlToImage": "https://media.wired.com/photos/638f959b54aee410695ffa12/191:100/w_1280,c_limit/Apple-Music-Sing-Featured-Gear.jpg",
"publishedAt": "2022-12-06T20:51:11Z",
"content": "When it comes to advanced technical features and seamless compatibility with iOS devices, Apple Music has Spotify well and truly beaten. The Swedish streaming giant has essentially the same content l… [+3348 chars]"
},
]
}
Response fashions,
Please put them into totally different information. I’ve put them into one code block to make it simpler to learn.
information class NewsResponse(
val articles: Listing<Article>,
val standing: String,
val totalResults: Int
)
information class Supply(
val id: String,
val title: String
)
information class Article(
val writer: String,
val content material: String,
val description: String,
val publishedAt: String,
val supply: Supply,
val title: String,
val url: String,
val urlToImage: String
)
Now let’s create API Service & repository, it’s going to be a easy one,
interface NewsApiService {
@GET("every little thing?q=apple&sortBy=recognition&apiKey=${Constants.API_KEY}&pageSize=20")
droop enjoyable getNews(
@Question("web page") web page: Int
): NewsResponse
}
That’s it. Now we are able to begin implementing pagination.
Paging Supply
Let’s begin by creating Paging Supply,
class NewsPagingSource(
non-public val newsApiService: NewsApiService,
): PagingSource<Int, Article>() {
override enjoyable getRefreshKey(state: PagingState<Int, Article>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}override droop enjoyable load(params: LoadParams<Int>): LoadResult<Int, Article> {
return attempt {
val web page = params.key ?: 1
val response = newsApiService.getNews(web page = web page)
LoadResult.Web page(
information = response.articles,
prevKey = if (web page == 1) null else web page.minus(1),
nextKey = if (response.articles.isEmpty()) null else web page.plus(1),
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
The first Paging library part within the repository layer is
PagingSource
. EveryPagingSource
object defines a supply of knowledge and methods to retrieve information from that supply. APagingSource
object can load information from any single supply, together with community sources and native databases.
In our instance, PagingSource
extends <Int, Article>
,
Int
is the kind of paging key, for our case it’s index numbers for pages.
Article
is the kind of information loaded.
getRefreshKey
, offers a key used for the preliminary load for the subsequent PagingSource
attributable to invalidation of this PagingSource
.
load
, perform will probably be referred to as by the Paging library to asynchronously fetch extra information to be displayed because the consumer scrolls round.
That’s it for PagingSource
, we are able to create repository & view mannequin.
Repository & View Mannequin
It’s probably not essential to have repository since PagingSource
acts like one, you’ll be able to take away repository and make the identical perform calls in view mannequin.
class NewsRepository @Inject constructor(
non-public val newsApiService: NewsApiService
) {
enjoyable getNews() = Pager(
config = PagingConfig(
pageSize = 20,
),
pagingSourceFactory = {
NewsPagingSource(newsApiService)
}
).stream
}
@HiltViewModel
class NewsViewModel @Inject constructor(
non-public val repository: NewsRepository,
): ViewModel() {enjoyable getBreakingNews(): Circulate<PagingData<Article>> = repository.getNews().cachedIn(viewModelScope)
}
The
Pager
part offers a public API for setting up situations ofPagingData
which can be uncovered in reactive streams, based mostly on aPagingSource
object and aPagingConfig
configuration object.
PagingConfig
, this class units choices concerning methods to load content material from a PagingSource
resembling how far forward to load, the dimensions request for the preliminary load, and others. The one obligatory parameter it’s a must to outline is the web page measurement
pagingSourceFactory
, perform that defines methods to create the PagingSource
.
That’s it. Now we are able to implement UI and see the outcomes.
UI Layer
collectAsLazyPagingItems
, collects values from thisCirculate
ofPagingData
and represents them inside aLazyPagingItems
occasion. TheLazyPagingItems
occasion can be utilized by thegadgets
anditemsIndexed
strategies fromLazyListScope
in an effort to show the info obtained from aCirculate
ofPagingData
.
First, we create LazyColumn
and inside it we use gadgets
which expects LazyPagingItems<T>
and set a singular worth for key. That’s it. We don’t should do something, as we fetch & paginate information will probably be inserted into LazyColumn
.
Since we additionally want to point when our information is being fetched, we’ll want to point out loading UI to customers. LazyPagingItems
comes for the rescue. LazyPagingItems<T>
has loadState
object which is CombinedLoadStates
.
CombinedLoadStates.supply
is a LoadStates
kind, with fields for 3 various kinds of LoadState
:
LoadStates.append
: For theLoadState
of things being fetched after the consumer’s present place.LoadStates.prepend
: For theLoadState
of things being fetched earlier than the consumer’s present place.LoadStates.refresh
: For theLoadState
of the preliminary load.
Every LoadState
itself will be one of many following:
LoadState.Loading
: Objects are being loaded.LoadState.NotLoading
: Objects are usually not being loaded.LoadState.Error
: There was a loading error.
For the preliminary load, we examine articles.loadState.refresh
and if state is LoadState.Loading
we present loading UI.
For the pagination, we examine articles.loadState.append
and if state is LoadState.Loading
once more and present loading UI.
You’ll find the complete code on the finish of the article.
That’s it. Let’s see the consequence.
Earlier than we begin, you may ask why will we reinvent the wheel? As a result of in some instances Paging 3 may cause boilerplate code and enhance the complexity. Implementing pagination with out Paging 3 may give us extra freedom and fewer boilerplate code.
Since we’ve already carried out ApiService
, we are able to begin by creating repository.
Repository
class NewsManuelPagingRepository @Inject constructor(
non-public val newsApiService: NewsApiService
) {
droop enjoyable getNews(web page: Int): Circulate<NewsResponse> = stream {
attempt {
emit(newsApiService.getNews(web page))
} catch (error: Exception) {
emit(NewsResponse(emptyList(), error.message ?: "", 0))
}
}.flowOn(Dispatchers.IO)
}
That is quite simple and poorly executed for our instance, and I don’t advocate you employ it this fashion in manufacturing. You’ll be able to examine these articles for extra data,
View Mannequin
Earlier than we create view mannequin, we’ll create enum class for Listing State.
enum class ListState {
IDLE,
LOADING,
PAGINATING,
ERROR,
PAGINATION_EXHAUST,
}
This enum class will assist us for managing state. Now we are able to create view mannequin.
First, we’ve got 3 variables,
web page
is for protecting the web page quantity. canPaginate
is to examine if we are able to paginate additional or if there may be any error. listState
is the state variable for the UI.
Within init
we make the primary request, we’re fetching first web page when view mannequin object created.
getNews
perform’s logic will be change relying on the endpoints and necessities. On this instance, we set listState
to Loading
or Paginating
relying on the web page
quantity and make the endpoint name.
Since endpoint returns standing: "okay"
for profitable request, we examine whether it is profitable or not. Whether it is profitable, we insert new gadgets to the record and set the values for canPaginate
and listState
.
That’s it. Logic could be very easy and open to enhancements. You’ll be able to take a look at it your self and alter it accordingly.
Lastly, let’s see the UI.
UI Layer
That is going to be just a little longer, so we’ll go half by half.
val viewModel = hiltViewModel<NewsManuelPagingViewModel>()
val lazyColumnListState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()val shouldStartPaginate = keep in mind {
derivedStateOf {
viewModel.canPaginate && (lazyColumnListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -9) >= (lazyColumnListState.layoutInfo.totalItemsCount - 6)
}
}
val articles = viewModel.newsList
lazyColumnListState
is important to get the seen merchandise data for Lazy Column.
shouldStartPaginate
is to find out whether or not or not we should always begin paginating. We’ll use derivedStateOf
for higher efficiency. You’ll be able to learn extra from this hyperlink.
First, we examine if we are able to paginate or not, viewModel.canPaginate
,
Then, we get the final seen merchandise’s index, lazyColumnListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
, and examine if the index quantity is greater than or equal to whole variety of merchandise rely, lazyColumnListState.layoutInfo.totalItemsCount
, minus some quantity that you simply determine. I made a decision to set it 6 for our case. You’ll be able to change it relying in your record and measurement.
LaunchedEffect(key1 = shouldStartPaginate.worth) {
if (shouldStartPaginate.worth && viewModel.listState == ListState.IDLE)
viewModel.getNews()
}
We’ll use LaunchedEffect
to begin pagination and make request. Every time shouldStartPaginate.worth
modifications, we begin the pagination and that’s it.
Now, we are able to create Lazy Column,
Setting state = lazyColumnListState
is essential to hear pagination, don’t overlook it!
I believe solely half that requires just a little little bit of an evidence is when(viewModel.listState)
and it’s quite simple. With the assistance of enum class that we’ve created earlier, we examine the state of the record and present needed UI.
You’ll find the complete code on the finish of the article.
That’s it. Let’s see the outcomes.
Full Code
MrNtlu/JetpackCompose-Pagination (github.com)