androidkotlinandroid-livedatakotlin-flow

How to collect multiple state flow in Android


How to collect two state flow in activity? Because mine only the first flow that consumed.

For example, inside viewmodel is like this:

class ExampleViewModel: ViewModel(){
    private val state = MutableStateFlow<HomeMainFragmentState>(HomeMainFragmentState.Init)
    private val products = MutableStateFlow<List<ProductEntity>>(mutableListOf())


    //to be consumed
    fun getState() : StateFlow<HomeMainFragmentState> = state
    fun getProducts() : StateFlow<List<ProductEntity>> = products
}

And then in my view is like this:

private fun observe(){
      viewLifecycleOwner.lifecycleScope.launch {
      viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED){
            viewModel.getState().collect { state -> handleState(state) }
            viewModel.getProducts().collect{ products -> handleProducts(products) }
        }
    }
}

The problem is, only the first flow is consumed, for this case is the 'state', the products was never consumed/executed by activity/fragment.

How to fix this? I also read about combining the flow, does it mean the second flow depends on first flow to run?


Solution

  • Calling collect on a Flow suspends the coroutine until the Flow is complete. For a MutableStateFlow, it will only be complete when it's cancelled. So usually, when you call collect on a Flow, you don't do anything else in the coroutine underneath that call.

    If you want to collect each of these flows individually, you need two coroutines.

    The flowOnLifecycle function will give you a bit cleaner code, so it's not as painful to have two coroutines:

    private fun observe(){
        viewModel.getState()
            .flowOnLifecycle(Lifecycle.State.STARTED)
            .onEach { state -> handleState(state) }
            .launchIn(lifecycleScope)
        viewModel.getProducts()
            .flowOnLifecycle(Lifecycle.State.STARTED)
            .onEach { products -> handleProducts(products) }
            .launchIn(lifecycleScope)
    }
    

    I also want to mention, function names like getState are unnatural in Kotlin, except when they are heavy functions (and even if it were a heavy function that has to calculate something, I would prefer generateState or calculateState). It's more proper to use a property:

    private val mutableState = MutableStateFlow<HomeMainFragmentState>(HomeMainFragmentState.Init)
    val state: StateFlow<HomeMainFragmentState> get() = mutableState
    

    They have said that in a future version of Kotlin, there will probably be a better syntax for exposing a read-only version of a mutable class that doesn't require a second property. Something like this:

    private val state = MutableStateFlow<HomeMainFragmentState>(HomeMainFragmentState.Init)
        public get(): StateFlow<HomeMainFragmentState>