I want to load network data first and combine it with data from my local room db.
So in that way, I would always present the newest state of the backend to my users (since its always requesting the network first) and update my ui when there are some local db updates.
A use-case example:
1. A UserActivity shows all created posts of a given user (through paging3).
Data has been fetched from network.
2. User starts a PostActivity where he/she likes the post (post was also visible in step 1 (UserActivity)).
This action will trigger a rest call where the result (updated post) will be stored to the local db.
3. User comes back to the UserActivity and sees that the post is marked liked now.
The UI update was done automatically, since the local Db changed in step 2.
Here are some of those things I’ve tried
Using Rooms PagingSource
implementation (LimitOffsetPagingSource
)
Initially I thought the Rooms PagingSource
+ RemoteMediator
is exactly what I needed (see this codelab example for implemantation).
The problem with that was though, that after the local database was initially filled with entities (through network), it will be used as the start source
for further inital fetches in pagings.
That means that changes in my backend would only be noticed by the UI when the user scrolls to the end of the list. Because only then, the LimitOffsetPagingSource
will request the RemoteMediator
again for further entities, since it is out of items.
A workaround (like used in the codelab) was to delete all entities before paging again.
// clear all tables in the database
if (loadType == LoadType.REFRESH) {
repoDatabase.remoteKeysDao().clearRemoteKeys()
repoDatabase.reposDao().clearRepos()
}
But this would break all my other views which also depended on these entity models.
Example:
1. TopPostsActivity shows top posts via paging
2. Clicking on a user which starts the UserActivity (from the first example)
3. UserActivity starts paging. This will initially delete all entities, so that we can fetch all new elements first.
User doesn't scroll much, so that not all entities are stored again.
4. User goes back to TopPostsActivity, where it will see an empty list. Reason for that is that the UserActivity deleted all depending posts before.
Only an additional refresh call can show the desired posts again.
Merging local and remote PagingData
manually
In this try, I used a custom PagingSource for fetching from network in my Pager (just the basics like here Defining a PagingSource).
Since Room can also provide flows
which notifies when there are some changes in the dataset, I transformed the data to a PagingData
and submitted it to my PagingAdapter
.
//Room DAO
@Query("SELECT * FROM post WHERE author_id = :authorId ORDER BY created_at DESC")
fun findAllByAuthor(authorId: String): Flow<PostWithAuthor>
The merge code
...
val localDbFlow = viewModel.repo.dao.findAllByAuthor(viewModel.author.id)
.map { PagingData.from(it) }
val networkFlow = viewModel.pager.flow
val mergedFlow = merge(localDbFlow, networkFlow)
flowAndCollectLatest(mergedFlow, Lifecycle.State.CREATED) {
adapter.submitData(it)
}
This also kinda worked, but the problem was that I am not getting any LoadState changes anymore due to the function PagingData.from(list)
.
/**
* Create a [PagingData] that immediately displays a static list of items when submitted to
* [AsyncPagingDataAdapter][androidx.paging.AsyncPagingDataAdapter].
*
* @param data Static list of [T] to display.
*/
@JvmStatic // Convenience for Java developers.
public fun <T : Any> from(data: List<T>): PagingData<T> = PagingData(
flow = flowOf(
PageEvent.Insert.Refresh(
pages = listOf(TransformablePage(originalPageOffset = 0, data = data)),
placeholdersBefore = 0,
placeholdersAfter = 0,
sourceLoadStates = LoadStates(
refresh = LoadState.NotLoading.Incomplete,
prepend = LoadState.NotLoading.Complete,
append = LoadState.NotLoading.Complete
)
)
),
receiver = NOOP_RECEIVER
)
So I can’t finish any refresh ui actions like with SwipeRefreshLayout
, since I’m not getting anything back through the LoadStateListener.
Can anyone please help me with this problem? Is there anything I’m overseeing? This issue really starts depressing me and steals the fun I have to code :'(
I can’t create a replacement for the automatically generated LimitOffsetPagingSource
(since It really seems critical for one without the required qualification).
You could try using the NetworkBoundResource architecture pattern. The idea is to have a repository layer that combines both the local database (using Room) and the network data. The repository layer returns a resource object that represents the state of the data being loaded, such as loading, success, and error.
You can define a NetworkBoundResource as follows: