Retryable StateFlow

Search for a command to run...

Really usefull, smart and well written.
I've been a professional software developer in the mobile space for around 15 years now. I started in the Android 2.1 / Eclipse / Ant days and iOS Objective-C days pre-ARC on Xcode 4 (if memory servic

Intro I recently learned about friendPaths as part of the Kotlin Gradle Plugin while browsing an Android Slack group. From the documentation on friendPaths: "Paths to the output directories of the friend modules whose internal declarations should be ...

If you've ever tried to compare the behavior of Row in Google's Jetpack Compose, HStack in Apple's SwiftUi, and something like display: flex in the web world, you'll see some interesting results. First, let's take a look at how HStack renders two lon...

Said another way, how to supercharge your development workflow with a debug drawer. As Android developers, we're constantly looking for ways to streamline our workflow. One often-overlooked tool that

As Jetpack Compose becomes more widely used across Android (and Multiplatform projects!), detecting regressions via tooling becomes more important to shift left and detect regressions earlier in the d

A common pattern used to observe the state of a feature can look something like this (borrowed from NowInAndroid):
ViewModel.kt
val uiState: StateFlow<MainActivityUiState> = userDataRepository.userId.map {
MainActivityUiState.Loaded(it)
}.stateIn(
scope = viewModelScope,
initialValue = MainActivityUiState.Loading,
started = SharingStarted.WhileSubscribed(5_000),
)
This works well for fetching some data, emitting a starting value, and emitting that data when the fetch is completed. Let's consider a scenario where that data fetching failed and we would like for our user to be able to retry the operation that powers our state.
For us to allow the user to be able to retry the operation(s) that power the state of the feature, we must first implement a mechanism that allows us to 'retry' the flow. This mechanism will be able to trigger a retry a flow to re-emit a value to a state provider.
class RetryableFlowTrigger {
internal val retryEvent: MutableStateFlow<RetryEvent> = MutableStateFlow(RetryEvent.INITIAL)
fun retry() {
retryEvent.value = RetryEvent.RETRYING
}
}
fun <T> RetryableFlowTrigger.retryableFlow(
flowProvider: RetryableFlowTrigger.() -> Flow<T>,
): Flow<T> {
return retryEvent
.onSubscription {
// reset to initial state on each new subscription so that the original flow can be re-evaluated
retryEvent.value = RetryEvent.INITIAL
}
.filter {
// allow retry and initial events to trigger the flow provider
it == RetryEvent.RETRYING || it == RetryEvent.INITIAL
}
.flatMapLatest {
// invoke the original flow provider
flowProvider.invoke(this)
}
.onEach {
// reset to idle on each value
retryEvent.value = RetryEvent.IDLE
}
}
internal enum class RetryEvent {
RETRYING,
INITIAL,
IDLE,
}
The usage of our new class looks like this:
ViewModel.kt
val retryableFlowTrigger = RetryableFlowTrigger()
val uiState : StateFlow<MainActivityUiState> = retryableFlowTrigger.retryableFlow {
userDataRepository.userId.map.map {
MainActivityUiState.Loaded(it)
}
}.stateIn(
scope = viewModelScope,
initialValue = MainActivityUiState.Loading,
started = SharingStarted.WhileSubscribed(5_000),
)
fun retryFlow() {
retryableFlowTrigger.retry()
}
Now, whenever you would like to provide UI to retry the flow, invoking retryFlow in your `ViewModel` will retry the flow.