*UPDATE*: I have provided a better Example in the thread. My issue was how to merge a “manual” mutab...
e
UPDATE: I have provided a better Example in the thread. My issue was how to merge a “manual” mutable flow with external sources. Original Question: How can i avoid declaring initialValue twice for StateFlow if i want a private
MutableStateFlow
for updating and a public
StateFlow
that uses
.stateIn()
Example:
Copy code
class ExampleViewModel: ViewModel() {
    private val _state = MutableStateFlow<Int>(0) // Set initial value
    
    val state: StateFlow<Int> = _state
        .stateIn(
            scope = ...,
            started = ...,
            initialValue = 0 // Have to duplicate initial value :(
        )
    
    fun onSomeAction() {
        _state.getAndUpdate { lastState -> lastState + 1 }
    }
}
j
Why do you use
stateIn
here?
It starts a coroutine to collect your existing state flow into a new state flow. You could just return the existing one under a read-only type
e
because i want the StateFlow to stay alive for the 5s if collector disconnects and needs to reconnect (i.e. activity recreates during config change). In Google’s NowInAndroid app, they use stateIn as well here:
Copy code
combine(<ExternalFlows>) {
   ...
}.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5_000),
    initialValue = AuthorScreenUiState(AuthorUiState.Loading, ...)
)
j
But you only need to control this lifetime because you start a coroutine. In the example you gave, there are transformations on other flows that are collected into the state flow. In your case, you already have an eternal state flow, no need for a coroutine
e
So for my learning can you verify if i concluded this right: • A) I should use
.stateIn()
for consuming cold flows into a StateFlow in the case i want those cold flows to stay active depending on the subscriber sharing behavior i defined? • B) If the ViewModel is the creator of a MutableStateFlow, i dont need to use .stateIn() since that mutable stateFlow is already hot and will stay alive for the lifecycle of the ViewModel, regardless if theres no collectors
👍🏻 1
Here’s a more detailed example of what I’m having to detail with. I want to map an external cold flow to State, but ALSO i want the the ability to publish state updates that are separate from those external flows. Any suggestions to improve this:
Copy code
class ExampleViewModel(externalIntFlow: Flow<Int>): ViewModel() {

    data class State(val msg: String, val isLoading: Boolean = false)

    private val _state = MutableSharedFlow<State>()

    val state = externalIntFlow.map { action ->
            when (action) {
                1 -> State("One")
                else -> State("?")
            }
        }.merge(_state)
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = State("Empty")
        )

    fun someAction() = viewModelScope.launch {
        /*
         Want to update last value of `state` but dont get benefit of state.getAndUpdate
         since state is not mutable from stateIn(), but instead have to use external sharedFlow + state.value.copy()
         */
        _state.emit(state.value.copy(isLoading = false))
        delay(1000)
        _state.emit(State("Success"))
    }
}
j
A) yes, in a way. To be clear, a state flow is really just a flow that has a state inside. If you want to turn cold flows into a state flow, you have to start a coroutine that consumes the cold flows and updates the state of the state flow, hence why
stateIn
needs a scope - to start this coroutine behind the scenes. And it's also why you define a policy to keep this coroutine alive or cancel it. If you create a mutable state flow directly, you just created some sort of observable state variable. It doesn't need any coroutine on its own, because the state will be updated by other pieces of code (they may use coroutines themselves, but they don't need to because state flows have a
state
property that can be updated without suspending). B) true
👍 1
For you current example, you could also define your mutable state flow with initial value, and then instead of using
stateIn
on the external cold flows, you could just manually launch a coroutine that collects them and updates your state flow. This way you can also update your state flow using
_state.value=x
in
someAction()
(it will not be read-only)
💡 1
e
Wow! super helpful Joffrey 🙂 So given your feedback, is this init{} block doing what you are describing:
Copy code
class ExampleViewModel(externalIntFlow: Flow<Int>): ViewModel() {

    data class State(val msg: String, val isLoading: Boolean = false)

    private val _state = MutableStateFlow(State("Empty"))
    val state = _state.asStateFlow()
    
    init {
        viewModelScope.launch { 
            externalIntFlow.map { action ->
                when (action) {
                    1 -> State("One")
                    else -> State("?")
                }
            }.also { _state.emitAll(it) }
        }
    }
    
    fun someAction() = viewModelScope.launch {
        _state.getAndUpdate { it.copy(isLoading = false) }
        delay(1000)
        _state.update { State("Success") }
    }
}
j
Yes exactly
🥳 1
e
if all you want to do is expose a non-mutable state flow to consumers,
Copy code
private val _state = MutableStateFlow<Int>(0)
val state: StateFlow<Int>
    get() = _state.asStateFlow()
j
That's exactly what OP did in the last snippet (the main issue was merging that "manual" mutable flow with external sources)