# Retryable StateFlow

A common pattern used to observe the state of a feature can look something like this (borrowed from [NowInAndroid](https://github.com/android/nowinandroid/blob/main/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt)):

```kotlin
    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.

## The Problem

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.

## The Solution

```kotlin
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:

```kotlin
    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.
