Let's say I am using an API to get a list of cars. This list is implemented and is working fine using a PagedList with a PageKeyedDataSource. The cars are showing up and when I scroll down, new cars are loaded and appended to the UI. Nice!
Now I want to show additional data for each car. Let's say the price for the car. This additional data is coming from another API endpoint. So as far as I understand, I have to find a way to use DataSource.map()
or DataSource.mapByPage()
to call the API for each car to receive its price. This price must then somehow be added to the list item.
I am using all the good stuff from the architecture components (LiveData, Data-binding, MVVM, and so on). For now I don't have a database and I would like to keep it like that. Retrofit is doing all the caching for me.
How would I approach this?
So it turned out that the only meaningful approach is to add the data from the second API call within the DataSource
. The reason for that is that the PagedList is considered immutable by the paging library. So before we either call LoadInitialCallback.onResult()
or LoadCallback.onResult()
all data should be prepared and available.
My DataSource looks like this now:
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Car>) {
val request = dataService.getCars(1) // load page 1 of all cars
val response = request.execute()
val items = response.body()?.values ?: emptyList()
// we got the initial batch of cars -> now check them for prices
fetchPrices(items, callback, nextPageKey = 2)
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Repository>) {
val request = dataService.getCars(params.key)
request.enqueue(object : Callback<Cars> {
override fun onResponse(call: Call<Cars>, response: Response<Cars>) {
if (response.isSuccessful) {
val items = response.body()?.values ?: emptyList()
retry = null
// we got a batch of cars -> now check them for prices
fetchPrices(items, afterCallback = callback, nextPageKey = params.key + 1)
}
}
override fun onFailure(call: Call<Repositories>, t: Throwable) {
...
}
})
}
/**
* This method is checking all of the given [cars] for having a price.
* If a car has a price, this is added to the car.
* If all cars were checked, the onResult-callback of this DataSource is called
* to signal the completion of getting the paged data.
*/
private fun fetchPrices(
repositories: List<Car>,
initialCallback: LoadInitialCallback<Int, Car>? = null,
afterCallback: LoadCallback<Int, Car>? = null,
nextPageKey: Int
) {
// used to count the server responses
var checkedPrices = 0
cars.forEach { car ->
// enqueue the price request for this car
val priceRequest = dataService.getPrice(car.id, 1)
priceRequest.enqueue(object : Callback<Prices> {
override fun onFailure(call: Call<Prices>, t: Throwable) {
// ignore failed requests, but still check for completion
// to increase the checkedPrices counter.
checkForCompletion()
}
override fun onResponse(call: Call<Cars>, response: Response<Cars>) {
if (!response.isSuccessful || response.body() == null) return
val prices = (response.body() as Prices).values
if (!prices.isNullOrEmpty()) {
car.price = prices.first()
}
checkForCompletion()
}
// check if we got server responses for all cars.
// If yes, call either the initialCallback or the afterCallback
private fun checkForCompletion() {
checkedPrices++
if (checkedPrices >= cars.size) {
initialCallback?.onResult(repositories, 0, nextPageKey)
afterCallback?.onResult(repositories, nextPageKey)
}
}
})
}
}