Pagination in Kotlin Multiplatform Mobile
Create faster loading pages with these strategies
Pagination is always unavoidable in a well-performed app not only to have efficient data loads but also to produce a better user experience. By dividing data into separated pages, the app will load faster, and the user will get the data sooner. It will lead to a better conversion rate than non-paging apps that require all data to be loaded.
When it comes to a question,
What’s one of easy ways to do pagination for declaratively constructed UI of a Kotlin Multiplatform Mobile (KMM) project?
There might be various answers, but we have no exact answers regarding practicality and reliability. Reading this article will let you explore the answer you might expect.
Requirement
It is recommended that you understand the KMM project structure. If you are still unfamiliar with it, please read it first here. It is also expected to be familiar with the declarative UI of Android (Jetpack Compose) and iOS (SwiftUI) platforms. Now let’s start with the shared module.
Shared
Since the KMM project is modular, we will start from the shared module. First, we need a data class consisting of the API response. The paginated API response generally has the current showing page, total pages, and message. Below is an example:
In the data class above, the page
is the current showing page. Meanwhile, themessage
is the server status message that exists only if an error occurs. Besides, we have also totalPages
showing the number of pages we should load.
To better work with the API fetching result, it is recommended that we wrap the result in a state. The following is the state example:
The state above wraps the API response and error in a shared module so that we just need to make one function in our shared view model that can return both possible results (API response or error occurrence). Now, let’s look at how the shared view model calls the API. Here’s the code:
In the MovieListSharedViewModel
above, we do not apply a try-catch function since it has been handled in the repository so that the loadMovie
function above will just call the result. This is applied to let us call the function more efficiently in the iOS workspace. Refer here to explore more.
We have now finished working with our shared module regardless of the use case and repository. We can now turn into our Android module to start implementing pagination for the Android platform.
Android
The declarative UI of the Android platform can be implemented using Jetpack Compose. Establishing pagination in Jetpack Compose is quite simple. We can use its paging library, which is easy to implement. To start, add the following dependency to the Android app Gradle.
implementation(“androidx.paging:paging-compose:1.0.0-alpha10”)
Now let’s make a data paging source to be called in our view
model:
In the load
the function above calls the function we have made in the shared view
model before. To collect the return result of the sharedViewModel.loadMovie
, we use .last()
since the loadMovie
function is wrapped by Flow. Finally, to make reusable result conditions, the return logic is handled in PagingHelper
class below:
The PagingHelper
class above is, indeed, created to have a reusable return function of the paging source load result so that we do not need to recreate return conditions once we have more than one paging source.
The next step is to create our list view model that will hold the list of our paging data so that it is aware of the app life cycle.
In the example above, the list is directly initialized. However, if we want to let it empty when the view model is initialized, we can set it to var list: Flow<PagingData<Movie>> = flowOf(PagingData.empty())
. This initialization also enables us to modify the list as necessary.
Finally, in our composable function, we can call the list by collecting the list in our view model by viewModel.list.collectAsLazyPagingItems()
. To see how it is implemented, check the code below:
However, the list has CombinedLoadStates
to be called to handle the error. Again, to have a reusable paging view that holds its error states, I created a custom paging view holder as follows:
Finally, we can now try our Android app. Below is the screenshot if we run the code:

iOS
We have made the pagination works in Android, and now it’s time to turn it into Xcode for implementing it in iOS. Let’s start by creating the view model or the observable class to be used as a state in our view configuration. Similar to Android, the view model should at least have a data list, a current showing page, and a throwable object that, in this case, I directly set as an error message. The view model should be as follows:
In the example above, we have a loading state namely loadingPage
that can be used once we need to use it in our UI configuration. Besides, there is also an error message set once we have either an IOException or status message from the server. As a piece of additional information, the sharedViewModel
is initialized in the MovieModule
configured in iOS main of the KMM project. Refer here to explore more.
Finally, the most important thing here is that we initialize the list differently from the Android view model since we need to use the append
function to add the list. That’s all the view model configuration we need. We can now start creating our UI function.
When showing a list of paging data in SwiftUI, we have to use LazyVStack to have better performance. Besides, we need to show a loading view based on the isLastPage
state we have in the view model. This is to be used to indicate that the scroll state has reached the last item.
From the above code, we can take a simple assumption on how the view model calls the loadPage
function in the List Loading View when it appears. However, once we have reached the last page, the loading view will not appear, and the view model will not call the loadPage
function.
What next? Yup, everything is done, and we can now run the app. We should have our app shows the paging data as follows:

Conclusion
When working on a multiplatform project, of course, what we expect is to have a two-in-one code for efficiency. In this project, we have applied a load page function in the shared movie list view model, which we can use in both Android Paging Data Source initialized in Android View Model and iOS View Model. However, we discuss only the core functions needed for pagination implementation.
To have a more complete implementation, please refer to the real project below: