Learn what’s involved to implement pagination in a clean-architecture news app.
This is what the final implementation will look like in the end:

The key ingredient is JetPack’s Paging 3 library.
Gradle dependencies:
In the Project level build.gradle:
Hilt Setup
Don’t forget to add the application name in the manifests file.
Now, we define the interface that contains retrofit-methods which make network requests.
Dependency Injection with Hilt Modules:
Note that Retrofit code uses builder pattern.
Interfaces and classes which use builder-pattern can’t be constructor-injected. Therefore, we need to create modules that are instructions to dagger-hilt on how to provide such dependencies.
We provide retrofit instances by creating functions annotated with @Provides
.
@Singleton
scopes this instance to the Application
container and makes it always provide the same instance.
The object AppModule
is annotated with @InstallIn(SingletonComponent::class)
which means that the bindings defined in this module are available in the Application
container.
Define the source of paging data:
This class also contains the logic of calling the methods in NewsApiInterface
based on query parameters declared in the constructor.
When the ‘query’ param is null, we intend to get top headlines based on ’country’, ‘language’, and ‘category’; whereas a non-null ‘query’ indicates a search in the news-API.
In this way, we abstract the logic of making network request based on query parameters from the repository layer.
In the ViewModel layer, we transform the data emitted by Pager.flow
based on queries received from the UI layer.
Flow is an asynchronous sequence of values.
Values are emitted by
Pager.flow
, transformed by intermediaries declared in the ViewModel, and ultimately consumed in the fragment.

The ViewModel part goes like this:
- Combine the query-flow and sortParams-flow using the
combine
operator. - Save the latest values of these streams in a data class and return them from the lambda.
- Switch from the above flow to the repository’s
Pager.flow
using theflatMapLatest
operator. - Cache the stream of data in
viewModelScope
using thecachedIn
operator from the Paging library
Till now we have defined the flow of data to the ViewModel layer, now let’s define how this data will be consumed in the UI layer.

This step updates the fragment’s recycler view, based on the observable stream of data.
Let us define a class that extends PagingDataAdapter
.
The time at which a particular news article was published is subtracted from the current date-time of the system (android device) to get ‘x no. of hours ago’, ‘x minutes ago’, and so on.
The implementation is as follows:
Check out this article by Jonathan Darwin to learn more.
Collect the Flow<PagingData>
in the Fragment.
- Instantiate the adapter class and assign it to a variable
- Set this variable to the fragment’s recycler view adapter
- Collect the flow from the ViewModel and submit the stream of data to the adapter.
That’s it, we have defined all the necessary components of the Paging library and implemented search functionality just like Google News.
Similar steps are to be followed when implementing fragments for top headlines (see the paging source implementation above).


Paging 3 library helps us to load data from a remote/local data source in small chunks called pages, thus allowing the app to use network bandwidth and system resources more efficiently.
But this approach involves a lot of boilerplate code.
You must have noticed that the above approach often complicates stuff, for example, if the page key is of type string and not Int.
In this case, we custom-define the
getRefreshKey
function in our paging source to update the page key (often called page-token, like in YouTube Data API) which is now part of the network response.