The Kotlin version of the Home APIs uses flows, a feature of the Kotlin language that provides powerful capabilities for handling asynchronous data streams, including transforming, filtering, mapping, converting to and from collections, and so forth.
Coroutines are closely associated with flows, and provide an elegant abstraction for writing asynchronous code and free the developer from having to explicitly write callback routines. Flows work hand-in-hand with coroutines, serving as a way to retrieve data from coroutines asynchronously, without having to wait for the completion of a function or a thread.
Data is retrieved from a flow by the consumer in a process called collecting.
Kotlin flows have a collect()
function that may be used for this purpose. What
happens in a flow during collection depends on the type of flow.
Kotlin flows (Flow<T>
) are cold by default, meaning that the flow builder
code produces data only when collect()
is called. A hot flow, by contrast,
produces data immediately, buffering it in memory like a queue for whenever the
data is consumed. Because it maintains data in memory, a hot flow is considered
to be stateful.
Flows may be converted from cold to hot using the shareIn
operator
(see Making cold flows hot using
shareIn
). You can also use the SharedFlow
or StateFlow
to turn a cold flow into a
hot flow.
How the sample app uses flows
The sample app uses view models in Jetpack Compose connected to the flows in the Home APIs. Many of the sample app UI elements are driven by state, but can be interacted with. The sample app also blends state from devices with an 'optimistic' state in the UI for a realtime user experience. The term optimistic means that the the app assumes that a given action succeeded and immediately updates the UI to reflect the expected outcome without waiting for confirmation. If it turns out that the action failed, the UI is updated to reflect the true state.
In the sample app, flows are created for each layer of the view model (structures,
rooms, devices). For example, it does this for structures with the following
call in GlobalViewModel.kt
:
fun getStructuresState(): StateFlow<List<StructureModel>?> =
homeClient
.structures()
.map { structures -> structures.map { StructureModel(it.name, it) }.sortedBy { it.name } }
.handleErrors()
.flowOn(Dispatchers.IO)
.stateIn(scope = viewModelScope, started = SharingStarted.WhileSubscribed(), null)
emitAll()
collects all the values from the given flow and emits them to the
collector. stateIn()
shares the most recently emitted value from a single
running instance of the upstream flow with multiple downstream subscribers. See
the
kotlinx.coroutines.flow
reference for more information.
Jetpack Compose
To store flow objects in local memory and prevent them from being terminated,
use the Kotlin
remember
API.
In Jetpack Compose, if you use this with
collectAsStateWithLifecycle()
,
Jetpack automatically manages subscribing and unsubscribing from flows based on
whether the application UI showing that state is actually in the foreground or
not.
A simple call in the sample app does this. Using the getStructuresState()
function shown earlier:
val structuresList by
remember(globalViewModel) { globalViewModel.getStructuresState() }.collectAsStateWithLifecycle()
Now, when any state for the structure changes (such as the name
), the
Composable function that uses it will reflect that updated state automatically.
In the sample app, this is the HomeActivityContent()
function.
Resources
For more information on Kotlin, flows, coroutines, and Jetpack Compose, see the following resources:
- Develop Android apps with Kotlin
- Learn Kotlin for Androids
- Kotlin coroutines on Android. These specific codelabs may be useful:
- Kotlin flows on Android and more specifically, StateFlow.
- State and Jetpack Compose, specifically the
collectAsStateWithLifecycle()
function. This function automatically manages subscribing and unsubscribing from the flows based on whether the UI showing that state is actually in the foreground or not. - If you're working with the Automation API, reading about Kotlin type-safe builders is useful in understanding how the Automation DSL works.