Subbu Lakshmanan

Don't expose Mutable Flow Types

Use best practices to avoid exposing mutable flow types

One of the recommendation in designing UI layer from the Android's guide to app architecture is to use 'Uni-Directional Data Flow'. This is achieved by viewModels exposing UI state via observer pattern and receive events from UI via callbacks.

One of the best practice in exposing the UI state is to use immutable data types like LiveData or StateFlow. But, sometimes, we may be tempted to use MutableStateFlow or MutableSharedFlow to expose the UI state. While they are very convenient for storing and adding updates of some data structures in event-driven paradigm & useful to manage such objects inside some class, it’s not recommended to expose them outside of the class.

Why

When properties of the types MutableStateFlow or MutableSharedFlow are accessible from outside of a class, data updates cannot be verified properly anymore.

val isBiometricEnabled = MutableStateFlow(false)

In the above example, the value of isBiometricEnabled can be changed from outside of the class. It is generally recommended to have only one class responsible for updating these flows, otherwise inconsistency issues and problems with maintainability, as well as bugs may be introduced.

What to do

To restrict write access, StateFlow or SharedFlow should be used together with private MutableStateFlow or MutableSharedFlow fields.

  • If the variable is used only for reading, change its type to StateFlow or SharedFlow
  • If the variable is used for writing, change its type to private MutableStateFlow or private MutableSharedFlow and add a public property with StateFlow or SharedFlow type that exposes the value of the private field

How to do

Here's one way of refactoring the code.

  1. Find the usages of the variable in question (i.e., one that has MutableStateFlow or MutableSharedFlow type)
  2. If the variable is used only for reading, change its type to StateFlow or SharedFlow
  3. If the variable is used for reading & writing,
  • Create a new variable with StateFlow or SharedFlow type that reads from the MutableStateFlow or MutableSharedFlow variable (use some naming convention to indicate that naming is temporary)

    val isBiometricEnabled = MutableStateFlow(false)
    val tempIsBiometricEnabled:StateFlow<Boolean> = isBiometricEnabled.asStateFlow()
    
  • Replace 'read' usages with the new variable

  • Change the type of the MutableStateFlow or MutableSharedFlow variable to private & rename it to indicate that it is private

    private val _isBiometricEnabled = MutableStateFlow(false)
    val tempIsBiometricEnabled:StateFlow<Boolean> = _isBiometricEnabled.asStateFlow()
    
  • Fix the naming of the new variable to match the naming convention

    private val _isBiometricEnabled = MutableStateFlow(false)
    val isBiometricEnabled:StateFlow<Boolean> = _isBiometricEnabled.asStateFlow()
    
  • Check the usages to verify that isBiometricEnabled is not used for writing anywhere outside of the class and _isBiometricEnabled is not exposed outside of the class

References

Here are some references to the articles that I found useful


All rights reserved