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
orSharedFlow
- If the variable is used for writing, change its type to
private MutableStateFlow
orprivate MutableSharedFlow
and add apublic
property withStateFlow
orSharedFlow
type that exposes the value of theprivate
field
How to do
Here's one way of refactoring the code.
- Find the usages of the variable in question (i.e., one that has
MutableStateFlow
orMutableSharedFlow
type) - If the variable is used only for reading, change its type to
StateFlow
orSharedFlow
- If the variable is used for reading & writing,
-
Create a new variable with
StateFlow
orSharedFlow
type that reads from theMutableStateFlow
orMutableSharedFlow
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
orMutableSharedFlow
variable toprivate
& rename it to indicate that it isprivate
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