Skip to content
This repository was archived by the owner on Jan 10, 2025. It is now read-only.

Commit 0bc6e42

Browse files
committed
Add documentation to LoadStatesMerger
1 parent 2cdb149 commit 0bc6e42

File tree

1 file changed

+49
-10
lines changed
  • PagingWithNetworkSample/lib/src/main/java/com/android/example/paging/pagingwithnetwork/reddit/paging

1 file changed

+49
-10
lines changed

PagingWithNetworkSample/lib/src/main/java/com/android/example/paging/pagingwithnetwork/reddit/paging/LoadStatesMerger.kt

+49-10
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
11
package com.android.example.paging.pagingwithnetwork.reddit.paging
22

3+
import androidx.annotation.VisibleForTesting
34
import androidx.paging.CombinedLoadStates
45
import androidx.paging.LoadState
5-
import androidx.paging.LoadState.*
6+
import androidx.paging.LoadState.NotLoading
7+
import androidx.paging.LoadState.Loading
68
import androidx.paging.LoadStates
7-
import com.android.example.paging.pagingwithnetwork.reddit.paging.MergedState.*
89
import kotlinx.coroutines.ExperimentalCoroutinesApi
910
import kotlinx.coroutines.flow.Flow
1011
import kotlinx.coroutines.flow.scan
1112
import kotlin.Error
13+
import androidx.paging.PagingDataAdapter
14+
import androidx.paging.RemoteMediator
15+
import androidx.paging.PagingSource
16+
import androidx.paging.LoadType.REFRESH
17+
import androidx.paging.LoadType
18+
import com.android.example.paging.pagingwithnetwork.reddit.paging.MergedState.NOT_LOADING
19+
import com.android.example.paging.pagingwithnetwork.reddit.paging.MergedState.REMOTE_STARTED
20+
import com.android.example.paging.pagingwithnetwork.reddit.paging.MergedState.REMOTE_ERROR
21+
import com.android.example.paging.pagingwithnetwork.reddit.paging.MergedState.SOURCE_ERROR
22+
import com.android.example.paging.pagingwithnetwork.reddit.paging.MergedState.SOURCE_LOADING
1223

24+
/**
25+
* Converts the raw [CombinedLoadStates] [Flow] from [PagingDataAdapter.loadStateFlow] into a new
26+
* [Flow] of [CombinedLoadStates] that track [CombinedLoadStates.mediator] states as they are
27+
* synchronously applied in the UI. Any [Loading] state triggered by [RemoteMediator] will only
28+
* transition back to [NotLoading] after the fetched items have been synchronously shown in UI by a
29+
* successful [PagingSource] load of type [REFRESH].
30+
*
31+
* Note: This class assumes that the [RemoteMediator] implementation always invalidates
32+
* [PagingSource] on a successful fetch, even if no data was modified (which Room does by default).
33+
* Using this class without this guarantee can cause [LoadState] to get indefinitely stuck as
34+
* [Loading] in cases where invalidation doesn't happen because the fetched network data represents
35+
* exactly what is already cached in DB.
36+
*/
1337
@OptIn(ExperimentalCoroutinesApi::class)
1438
fun Flow<CombinedLoadStates>.asMergedLoadStates(): Flow<LoadStates> {
1539
val syncRemoteState = LoadStatesMerger()
@@ -30,18 +54,25 @@ private class LoadStatesMerger {
3054
private set
3155
var append: LoadState = NotLoading(endOfPaginationReached = false)
3256
private set
33-
private var refreshState: MergedState = NOT_LOADING
34-
private var prependState: MergedState = NOT_LOADING
35-
private var appendState: MergedState = NOT_LOADING
57+
var refreshState: MergedState = NOT_LOADING
58+
private set
59+
var prependState: MergedState = NOT_LOADING
60+
private set
61+
var appendState: MergedState = NOT_LOADING
62+
private set
3663

3764
fun toLoadStates() = LoadStates(
3865
refresh = refresh,
3966
prepend = prepend,
4067
append = append
4168
)
4269

43-
internal fun updateFromCombinedLoadStates(combinedLoadStates: CombinedLoadStates) {
44-
computeSynchronousRemoteStates(
70+
/**
71+
* For every new emission of [CombinedLoadStates] from the original [Flow], update the
72+
* [MergedState] of each [LoadType] and compute the new [LoadState].
73+
*/
74+
fun updateFromCombinedLoadStates(combinedLoadStates: CombinedLoadStates) {
75+
computeNextLoadStateAndMergedState(
4576
sourceRefreshState = combinedLoadStates.source.refresh,
4677
sourceState = combinedLoadStates.source.refresh,
4778
remoteState = combinedLoadStates.mediator?.refresh,
@@ -50,7 +81,7 @@ private class LoadStatesMerger {
5081
refresh = it.first
5182
refreshState = it.second
5283
}
53-
computeSynchronousRemoteStates(
84+
computeNextLoadStateAndMergedState(
5485
sourceRefreshState = combinedLoadStates.source.refresh,
5586
sourceState = combinedLoadStates.source.prepend,
5687
remoteState = combinedLoadStates.mediator?.prepend,
@@ -59,7 +90,7 @@ private class LoadStatesMerger {
5990
prepend = it.first
6091
prependState = it.second
6192
}
62-
computeSynchronousRemoteStates(
93+
computeNextLoadStateAndMergedState(
6394
sourceRefreshState = combinedLoadStates.source.refresh,
6495
sourceState = combinedLoadStates.source.append,
6596
remoteState = combinedLoadStates.mediator?.append,
@@ -70,7 +101,11 @@ private class LoadStatesMerger {
70101
}
71102
}
72103

73-
private fun computeSynchronousRemoteStates(
104+
/**
105+
* Compute which [LoadState] and [MergedState] to transition, given the previous and current
106+
* state for a particular [LoadType].
107+
*/
108+
private fun computeNextLoadStateAndMergedState(
74109
sourceRefreshState: LoadState,
75110
sourceState: LoadState,
76111
remoteState: LoadState?,
@@ -111,6 +146,10 @@ private class LoadStatesMerger {
111146

112147
/**
113148
* State machine used to compute [LoadState] values in [LoadStatesMerger].
149+
*
150+
* This allows [LoadStatesMerger] to track whether to block transitioning to [NotLoading] from the
151+
* [Loading] state if it was triggered by [RemoteMediator], until [PagingSource] invalidates and
152+
* completes [REFRESH].
114153
*/
115154
private enum class MergedState {
116155
/**

0 commit comments

Comments
 (0)