Bradleycorn
12/11/2023, 2:00 PMisOffline: StateFlow<Boolean>
property.
And, the NiaApp Composable collects the StateFlow and uses a LaunchedEffect
to show a snackbar when it’s value changes.
The use of a LaunchedEffect in the Composable makes sense in this case, since it needs to use the snackabarHostState, which is owned by the NiaApp
composable.
But say that when the app goes offline we want to navigate to a destination instead of showing a snackbar, and that navigation action is handled in the NiaAppState
model.
So, we might make a simple update in the NiaApp
like this:
val isOffline by appState.isOffline.collectAsStateWithLifecycle()
LaunchedEffect(isOffline) {
if (isOffline) {
// snackbarHostState.showSnackbar(...) <-- Remove this
appState.navigateToOfflineScreen() // and replace with this
}
}
That works fine. But now it seems like the collection of the StateFlow and handling of it’s values in the NiaApp
composable via a Launched effect is not really necessary. It’s not doing anything that requires NiaApp
or any values it owns. We could do (nearly?) all of this in the NiaAppState
model, in a few ways:
1. Add an onEach
to the isOffline
StateFlow and do the navigation as necessary in it’s lambda. In NiaApp
, we’d just do appState.isOffline.collectAsStateWithLifecycle()
and eliminate the LaunchedEffect
.
2. NiaAppState
has a CoroutineScope
. We could add an init
block and use the CoroutineScope to collect the isOffline flow, and navigate as necessary:
class NiaAppState(....) {
init {
coroutineScope.launch {
networkMonitor.isOnline.collect { isOnline ->
if (!isOnline) { navController.navigateToOfflineScreen() }
}
}
}
}
So, when we have a state that is not driven by user generated events (like the network monitoring above), and the actions that we need to take are not a direct update of the current interface (like using the navcontroller to navigate somewhere) … what’s the best way to handle that?Zach Klippenstein (he/him) [MOD]
12/11/2023, 4:02 PMZach Klippenstein (he/him) [MOD]
12/11/2023, 4:03 PMBradleycorn
12/11/2023, 4:04 PMZach Klippenstein (he/him) [MOD]
12/11/2023, 4:05 PMRoman Levinzon
12/11/2023, 4:08 PMSomethingCoordinator
that would manage the UI part of the feature, coordinate different states of the Screen, including ViewModels, NavControllers, Composable states and etc. Described more thoroughly here: https://medium.com/@levinzon-roman/jetpack-compose-ui-architecture-a34c4d3e4391Bradleycorn
12/11/2023, 4:14 PMBut if your app state has its own coroutine scope, it seems like this work should be scoped to that.yeah, the AppState does have a coroutine scope, and the other thing I thought of was using an
onEach
on a StateFlow for the offline/online value, like this:
val isOffline = networkMonitor.isOnline
.map(Boolean::not)
.onEach { isOffline ->
if (isOffline) { navController.navigateToOfflineScreen() }
}
.stateIn(
scope = coroutineScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = false,
)
And then just collect the flow in the NiaApp
composable:
appState.isOffline.collectAsSateWithLifecycle()
Zach Klippenstein (he/him) [MOD]
12/11/2023, 4:18 PMBradleycorn
12/11/2023, 4:18 PMonEach
here is kind of like hiding the side effect.
So, I tend to think I’ll just keep the LaunchedEffect
because it makes it explicit what is going on.Bradleycorn
12/11/2023, 4:18 PMZach Klippenstein (he/him) [MOD]
12/11/2023, 4:18 PMonActive
hook or something, i would launch the coroutine there