Tim Malseed
12/21/2021, 10:40 PMsealed class ScreenAState() {
object Loading : ScreenAState()
object Ready : ScreenAState()
}
sealed class ScreenBState() {
object Loading : ScreenAState()
object Ready : ScreenAState()
}
sealed class Event {
object DataSuccessfullyRetrieved : Event()
}
data class BackendDataObject(val stuff: Int)
class SharedViewModel {
val screenAState = MutableStateFlow<ScreenAState>(ScreenAState.Loading)
val screenBState = MutableStateFlow<ScreenAState>(ScreenAState.Loading)
val events = MutableSharedFlow<Event>(
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
// Some data that is set in Screen A, but retrieved in Screen B
val someSharedObject = MutableStateFlow<BackendDataObject?>(null)
fun submitThingFromScreenA(thing: String) {
screenAState.value = ScreenAState.Loading
backend.getDataObject(thing)
.onSuccess {
someSharedObject.value = it
events.emit(Event.DataSuccessfullyRetrieved)
}
}
}
@Composable
fun ScreenA(viewModel: SharedViewModel, onNavigateToScreenB: () -> Unit) {
LaunchedEffect(viewModel) {
viewModel.events
.onEach {
onNavigateToScreenB()
}
.launchin(this)
}
Button(
onClick = {
viewMode.submitThingFromScreenA("thing")
}
)
}
@Composable
fun ScreenB(viewModel: SharedViewModel) {
val sharedObject by viewModel.someSharedObject.collectAsState()
...
}
screenBState
is not scoped to the lifecycle of ScreenB. If you navigate to Screen B and then press back, screenBState
will not change, and the someSharedObject
will still be valid (though technically stale)BackendDataObject
from ScreenA to ScreenB. I’m using Compose Navigation. Serializing/Parcelizing the object and passing it that way doesn’t seem right. I could create some singleton repository and hold the BackendDataObject
there, but then that repository object will outlive the scope of both these screens.BackendDataObject
.
That would look like this:
class ScreenAViewModel {
val screenAState = MutableStateFlow<ScreenAState>(ScreenAState.Loading)
val events = MutableSharedFlow<Event>(
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
fun submitThingFromScreenA(thing: String) {
screenAState.value = ScreenAState.Loading
backend.getDataObject(thing)
.onSuccess {
someSharedObject.value = it
events.emit(Event.DataSuccessfullyRetrieved)
}
}
}
class ScreenBViewModel {
val screenBState = MutableStateFlow<ScreenAState>(ScreenAState.Loading)
}
class SharedViewModel {
// Some data that is set in Screen A, but retrieved in Screen B
val someSharedObject = MutableStateFlow<BackendDataObject?>(null)
}
@Composable
fun ScreenA(screenAViewModel: ScreenAViewModel, sharedViewModel: SharedViewModel, onNavigateToScreenB: () -> Unit) {
LaunchedEffect(screenAViewModel) {
screenAViewModel.events
.onEach { event ->
when (event) {
is Event.DataSuccessfullyRetrieved -> {
sharedViewModel.someSharedObject = event.data
onNavigateToScreenB()
}
}
}
.launchin(this)
}
Button(
onClick = {
screenAViewModel.submitThingFromScreenA("thing")
}
)
}
Alex Vanyo
12/21/2021, 11:10 PMSharedViewModel
will be recreated from scratch, so you’ll find that its shared object will be null
in that case.
If the data is sufficiently small, a SavedStateHandle
could be used to persist the shared object. Otherwise, you may need to have ScreenB
navigate back to ScreenA
if the shared object is null
.Tim Malseed
12/21/2021, 11:11 PM