8. Flows
In Kotlin, a Flow
is an asynchronous stream of values. Values will flow down the stream, and there are collectors that can observe these values. Flow
is probably best explained by example, so let's go over some common use-cases for Flow
s in Android Development
Example 0: Pure flow example
Here we have a very pure example of how a Flow
can be used. I took this from creating a flow on the Android developer documentation since it is a very nice first flow usage example. Here we use flow
, which is a flow builder that allows us to run suspend
functions inside and emit
their results to the flow. We could observe this flow using latestNews.collect
, so then whenever the emit
function is called, the lambda we pass in to latestNews.collect
is also called. Let's see how we could observe this Flow
in the UI.
We can use the collectAsState
method. collect
is a method on a Flow that suspends the code block until the FlowCollector
finishes emitting values. In this case, our FlowCollector
is the while(true)
block from the first code snippet, so collect
block suspends indefinitely. However, collectAsState
runs collect
in a coroutine and transforms the results into a State<List<String>>
, and as we know whenever the State
value changes, our UI recomposes and automatically updates. This is one of the benefits of reactive UI. So even though running a lambda on each flow value emission is a more of an imperative idea, we can still use it in a reactive (declarative) context.
Example 1: Account creation validation
Let's say that the user is typing in their account information, and we want the UI to update live based on whether their information is valid. This could be if their email is valid, if their password is secure enough, etc.. But for this demo, we're going to do a basic version with just username and password. Let's walk through the ViewModel code to see how it uses Flow
to streamline this idea.
Let's walk through this code. You'll first notice that password
and username
are MutableStateFlow
s. A MutableStateFlow
is a type of Flow
that holds a state, and it emits new values whenever its state is updated. You may be wondering, why not just use a state to represent these values? The main reason is that we want to be able to launch an operation whenever either of these values updates. States are automatically observed by the UI, since whenever a state's value changes the UI recomposes. But we don't have a good way of observing state updates ourselves. Flow
emissions however are easily observed through the combine
function. Whenever the username
or password
flows have an emission, the combine
function is called, and we update the UiState
accordingly.
So we use combine
to create a new Flow
that is a result of applying the transform functions to the values from the emissions of username
and password
. The thing is, the combine
method alone just initializes this new Flow
, but it doesn't actually collect
it, so the transform lambda we wrote won't be called. That's why we use launchIn
, which collect
s the flow in a certain CoroutineScope
. We use viewModelScope
, so when our ViewModel gets disposed, our coroutine will get disposed with it. and we don't have a memory leak where we are using resources to constantly observe this Flow
.
Some other design decisions for this ViewModel include making all the MutableStateFlow
s private. This was done for separation of concerns, so we don't have to worry about the UI updating our ViewModel flows however they want to. The only functions that mutate the state that we expose to the UI are updatePassword
and updateUsername
, so now we know exactly where updates to this ViewModel's state will be coming from: usages of those functions. We expose the uiState with asStateFlow
, which makes it read-only.
Last updated