提交 95d22727 编写于 作者: D Dustin Lam

Add a sample showing how to synchronously await for remote loads to be applied

上级 8f4936b3
......@@ -31,6 +31,7 @@ import androidx.paging.LoadState
import com.android.example.paging.pagingwithnetwork.GlideApp
import com.android.example.paging.pagingwithnetwork.databinding.ActivityRedditBinding
import com.android.example.paging.pagingwithnetwork.reddit.ServiceLocator
import com.android.example.paging.pagingwithnetwork.reddit.paging.asHelperStates
import com.android.example.paging.pagingwithnetwork.reddit.repository.RedditPostRepository
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
......@@ -85,7 +86,7 @@ class RedditActivity : AppCompatActivity() {
lifecycleScope.launchWhenCreated {
adapter.loadStateFlow.collectLatest { loadStates ->
binding.swipeRefresh.isRefreshing = loadStates.refresh is LoadState.Loading
binding.swipeRefresh.isRefreshing = loadStates.mediator?.refresh is LoadState.Loading
}
}
......@@ -97,10 +98,15 @@ class RedditActivity : AppCompatActivity() {
lifecycleScope.launchWhenCreated {
adapter.loadStateFlow
// Only emit when REFRESH LoadState for RemoteMediator changes.
// Use a state-machine to track LoadStates such that we only transition to
// NotLoading from a RemoteMediator load if it was also presented to UI.
.asHelperStates()
// Only emit when REFRESH changes, as we only want to react on loads replacing the
// list.
.distinctUntilChangedBy { it.refresh }
// Only react to cases where Remote REFRESH completes i.e., NotLoading.
// Only react to cases where REFRESH completes i.e., NotLoading.
.filter { it.refresh is LoadState.NotLoading }
// Scroll to top is synchronous with UI updates, even if remote load was triggered.
.collect { binding.list.scrollToPosition(0) }
}
}
......
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.HelperState.*
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.
*/
class CombinedLoadStatesHelper {
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: HelperState = NOT_LOADING
private var prependState: HelperState = NOT_LOADING
private var appendState: HelperState = NOT_LOADING
fun toLoadStates() = LoadStates(
refresh = refresh,
prepend = prepend,
append = append
)
internal fun updateFromCombinedLoadStates(combinedLoadStates: CombinedLoadStates) {
computeHelperStates(
sourceRefreshState = combinedLoadStates.source.refresh,
sourceState = combinedLoadStates.source.refresh,
remoteState = combinedLoadStates.mediator?.refresh,
helperState = refreshState,
).also {
refresh = it.first
refreshState = it.second
}
computeHelperStates(
sourceRefreshState = combinedLoadStates.source.refresh,
sourceState = combinedLoadStates.source.prepend,
remoteState = combinedLoadStates.mediator?.prepend,
helperState = prependState,
).also {
prepend = it.first
prependState = it.second
}
computeHelperStates(
sourceRefreshState = combinedLoadStates.source.refresh,
sourceState = combinedLoadStates.source.append,
remoteState = combinedLoadStates.mediator?.append,
helperState = appendState,
).also {
append = it.first
appendState = it.second
}
}
private fun computeHelperStates(
sourceRefreshState: LoadState,
sourceState: LoadState,
remoteState: LoadState?,
helperState: HelperState,
): Pair<LoadState, HelperState> {
if (remoteState == null) return sourceState to NOT_LOADING
return when (helperState) {
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)
fun Flow<CombinedLoadStates>.asHelperStates(): Flow<LoadStates> {
val helper = CombinedLoadStatesHelper()
return scan(helper.toLoadStates()) { _, combinedLoadStates ->
helper.updateFromCombinedLoadStates(combinedLoadStates)
helper.toLoadStates()
}
}
/**
* State machine used to compute [LoadState] values in [CombinedLoadStatesHelper].
*/
enum class HelperState {
/**
* 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,
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册