Pagination in Jetpack Compose with and with out Paging 3


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. Every PagingSource object defines a supply of knowledge and methods to retrieve information from that supply. A PagingSource 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 of PagingData which can be uncovered in reactive streams, based mostly on a PagingSource object and a PagingConfig 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 this Circulate of PagingData and represents them inside a LazyPagingItems occasion. The LazyPagingItems occasion can be utilized by the gadgets and itemsIndexed strategies from LazyListScope in an effort to show the info obtained from a Circulate of PagingData.

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 the LoadState of things being fetched after the consumer’s present place.
  • LoadStates.prepend: For the LoadState of things being fetched earlier than the consumer’s present place.
  • LoadStates.refresh: For the LoadState 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.

Paging 3 Pagination

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 derivedStateOffor 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.

Pagination with out Paging 3

Full Code

MrNtlu/JetpackCompose-Pagination (github.com)

Sources:

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles