Search code examples
androidkotlinandroid-jetpack-composekotlin-flowkotlin-stateflow

Flow not observing changes in a variable


I need that when the var called CustomSingleton.customVar stored in a singleton is modified, my composable gets recomposed to change the current screen in the navhost. So I did this on the viewmodel of the screen that contains the navhost:

val currentScreenId = MutableStateFlow<Int>(CustomSingleton.customVar)

val currentScreenIdState: StateFlow<Int> = currentScreenId.stateIn(
    viewModelScope,
    SharingStarted.WhileSubscribed(5_000),
    CustomSingleton.customVar
)

I listen for the value of the flow on the composable using this code:

val currentSectionId by viewModel.currentScreenIdState.collectAsStateWithLifecycle()

Then, some other part of the code modifies the variable on the singleton:

CustomSingleton.customVar = 1

The issue is that the composable is not being recomposed so the change of the variable is not being observed.

Why?


Solution

  • As Gabe Sechan already pointed out the parameter of MutableStateFlow() is the initial value, it is not automatically observed for changes.

    If you want to observe CustomSingleton.customVar for changes that itself must be a flow. Assuming customVar is currently declared like this in CustomSingleton:

    var customVar = 0
    

    Then you can change it to this:

    val customVar = MutableStateFlow(0)
    

    0 is the initial value here, replace it with whatever you want. Since this is a Flow now and not an Int, changing the value is done like this:

    customVar.value = 42
    

    Now, with this out of the way you can change your view model code to this:

    val currentScreenIdState: StateFlow<Int> = CustomSingleton.customVar.asStateFlow()
    

    And that's it. Whenever the value of customVar is changed your currentScreenIdState flow emits a new value that is then collected by your UI.


    The key is to already use flows at the source for everything that you need to observe for changes. MutableStateFlow behaves similar to a variable as it always has a single value. But you can use any other type of flow as well (then you maybe need to use stateIn in the view model again, instead of asStateFlow). What's best depends on where the changes are actually coming from.

    When it is explicitly set by some other parts of your code a MutableStateFlow is a good choice. When the changed value comes from a callback, then you can convert that into a flow with callbackFlow. When the data comes from some other API like a database then that may already support flows out-of-the-box. Room, for example, can easily be configured to return all database results as a flow.