This repository was archived by the owner on Jan 10, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 10
Add a sample showing how to synchronously await for remote loads to be applied #987
Merged
Merged
Changes from 2 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
95d2272
Add a sample showing how to synchronously await for remote loads to b…
dlam a4b50ab
Rename CombinedLoadStatesHelper to SynchronousRemoteStates
dlam 2cdb149
Rename SynchronousRemoteStates to MergedLoadStates
dlam 0bc6e42
Add documentation to LoadStatesMerger
dlam File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
141 changes: 141 additions & 0 deletions
141
...ava/com/android/example/paging/pagingwithnetwork/reddit/paging/SynchronousRemoteStates.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package com.android.example.paging.pagingwithnetwork.reddit.paging | ||
|
||
import androidx.paging.CombinedLoadStates | ||
import androidx.paging.LoadState | ||
import androidx.paging.LoadState.* | ||
import androidx.paging.LoadStates | ||
import com.android.example.paging.pagingwithnetwork.reddit.paging.SynchronousRemoteState.* | ||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.scan | ||
import kotlin.Error | ||
|
||
/** | ||
* Track the combined [LoadState] of [RemoteMediator] and [PagingSource], so that each load type | ||
* is only set to [NotLoading] when [RemoteMediator] load is applied on presenter-side. | ||
*/ | ||
private class SynchronousRemoteStates { | ||
dlam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var refresh: LoadState = NotLoading(endOfPaginationReached = false) | ||
private set | ||
var prepend: LoadState = NotLoading(endOfPaginationReached = false) | ||
private set | ||
var append: LoadState = NotLoading(endOfPaginationReached = false) | ||
private set | ||
private var refreshState: SynchronousRemoteState = NOT_LOADING | ||
private var prependState: SynchronousRemoteState = NOT_LOADING | ||
private var appendState: SynchronousRemoteState = NOT_LOADING | ||
|
||
fun toLoadStates() = LoadStates( | ||
refresh = refresh, | ||
prepend = prepend, | ||
append = append | ||
) | ||
|
||
internal fun updateFromCombinedLoadStates(combinedLoadStates: CombinedLoadStates) { | ||
dlam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
computeSynchronousRemoteStates( | ||
sourceRefreshState = combinedLoadStates.source.refresh, | ||
sourceState = combinedLoadStates.source.refresh, | ||
remoteState = combinedLoadStates.mediator?.refresh, | ||
synchronousRemoteState = refreshState, | ||
).also { | ||
refresh = it.first | ||
refreshState = it.second | ||
} | ||
computeSynchronousRemoteStates( | ||
sourceRefreshState = combinedLoadStates.source.refresh, | ||
sourceState = combinedLoadStates.source.prepend, | ||
remoteState = combinedLoadStates.mediator?.prepend, | ||
synchronousRemoteState = prependState, | ||
).also { | ||
prepend = it.first | ||
prependState = it.second | ||
} | ||
computeSynchronousRemoteStates( | ||
sourceRefreshState = combinedLoadStates.source.refresh, | ||
sourceState = combinedLoadStates.source.append, | ||
remoteState = combinedLoadStates.mediator?.append, | ||
synchronousRemoteState = appendState, | ||
).also { | ||
append = it.first | ||
appendState = it.second | ||
} | ||
} | ||
|
||
private fun computeSynchronousRemoteStates( | ||
sourceRefreshState: LoadState, | ||
sourceState: LoadState, | ||
remoteState: LoadState?, | ||
synchronousRemoteState: SynchronousRemoteState, | ||
dlam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
): Pair<LoadState, SynchronousRemoteState> { | ||
if (remoteState == null) return sourceState to NOT_LOADING | ||
|
||
return when (synchronousRemoteState) { | ||
NOT_LOADING -> when (remoteState) { | ||
is Loading -> Loading to REMOTE_STARTED | ||
is Error -> remoteState to REMOTE_ERROR | ||
else -> NotLoading(remoteState.endOfPaginationReached) to NOT_LOADING | ||
} | ||
REMOTE_STARTED -> when { | ||
remoteState is Error -> remoteState to REMOTE_ERROR | ||
sourceRefreshState is Loading -> Loading to SOURCE_LOADING | ||
else -> Loading to REMOTE_STARTED | ||
} | ||
REMOTE_ERROR -> when (remoteState) { | ||
is Error -> remoteState to REMOTE_ERROR | ||
else -> Loading to REMOTE_STARTED | ||
} | ||
SOURCE_LOADING -> when { | ||
sourceRefreshState is Error -> sourceRefreshState to SOURCE_ERROR | ||
remoteState is Error -> remoteState to REMOTE_ERROR | ||
sourceRefreshState is NotLoading -> { | ||
NotLoading(remoteState.endOfPaginationReached) to NOT_LOADING | ||
} | ||
else -> Loading to SOURCE_LOADING | ||
} | ||
SOURCE_ERROR -> when (sourceRefreshState) { | ||
is Error -> sourceRefreshState to SOURCE_ERROR | ||
else -> sourceRefreshState to SOURCE_LOADING | ||
} | ||
} | ||
} | ||
} | ||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
dlam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fun Flow<CombinedLoadStates>.asSynchronousRemoteStates(): Flow<LoadStates> { | ||
dlam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
val syncRemoteState = SynchronousRemoteStates() | ||
return scan(syncRemoteState.toLoadStates()) { _, combinedLoadStates -> | ||
syncRemoteState.updateFromCombinedLoadStates(combinedLoadStates) | ||
syncRemoteState.toLoadStates() | ||
} | ||
} | ||
|
||
/** | ||
* State machine used to compute [LoadState] values in [SynchronousRemoteStates]. | ||
*/ | ||
enum class SynchronousRemoteState { | ||
dlam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/** | ||
* Idle state; defer to remote state for endOfPaginationReached. | ||
*/ | ||
NOT_LOADING, | ||
|
||
/** | ||
* Remote load triggered; start listening for source refresh. | ||
*/ | ||
REMOTE_STARTED, | ||
|
||
/** | ||
* Waiting for remote in error state to get retried | ||
*/ | ||
REMOTE_ERROR, | ||
|
||
/** | ||
* Source refresh triggered by remote invalidation, once this completes we can be sure | ||
* the next generation was loaded. | ||
*/ | ||
SOURCE_LOADING, | ||
|
||
/** | ||
* Remote load completed, but waiting for source refresh in error state to get retried. | ||
*/ | ||
SOURCE_ERROR, | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you have tests from when you were experimenting with this in the paging codebase?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do but it might take some time to adapt: https://android-review.googlesource.com/c/platform/frameworks/support/+/1591652/7/paging/common/src/main/kotlin/androidx/paging/MutableLoadStateCollection.kt
Let me leave this as a follow-up? I have a couple things on the queue I should probably do before migrating those tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM