alaershov
05/26/2023, 11:32 AMmove()
.
Another example is https://google.github.io/accompanist/permissions/ - is has a
rememberPermissionState()
which creates a PermissionState state-holder which can be used to call methods like launchPermissionRequest()
.
In some cases it can be called directly from a callback or a LaunchedEffect
inside a composable, but there are situations where I want to call it from the ViewModel
.
Google Maps for Compose use a similar approach - a state holder is introduced, and you can call imperative methods on this state holder to do some side-effects.
One way to do this is to hoist rememberPermissionState()
to ViewModel, but IMO it makes the VM less clean - it now depends on a whole Compose library and whatnot. And if I use some strict UDF approach like MVI, I can't have some weird PermissionState in my screen state.
Another way to solve this is to pass some Flow of events to compose, collect them in an Effect, and call PermissionState from there, but is feels a bit like a hack.
Can you recommend some clean and practical approach for such situations? What are your thoughts of the approaches I mentioned?Landry Norris
05/26/2023, 1:03 PMalaershov
05/26/2023, 1:04 PMLandry Norris
05/26/2023, 1:08 PMalaershov
05/26/2023, 1:12 PMChris Fillmore
05/26/2023, 2:47 PMAnother way to solve this is to pass some Flow of events to compose, collect them in an EffectIf your UI truly needs to consume events (rather than just reflect state), then I think this approach is the correct one and not a hack. I think for any given situation, you need to investigate whether you definitely need this.
In some cases it can be called directly from a callback or aCan you elaborate on this? Why do you want to invoke permissions methods from your VM?inside a composable, but there are situations where I want to call it from theLaunchedEffect
.ViewModel
alaershov
05/26/2023, 2:57 PMButton(onClick = { cameraPermissionState.launchPermissionRequest() })
I'm not a fan of implementing button click logic directly in Compose, and would prefer it co call ViewModel, and then launch permission from there. The logic could be more complex - maybe some some logging, maybe something suspending work before launchPermissionRequest
, maybe even launchPermissionRequest
by some other event, not related of button click.
Permission request is just an example, the same pattern could be applied to other situations. Move the map when the user's location changes is another example, you get the idea.Chris Fillmore
05/26/2023, 3:08 PMclass MyViewModel : ViewModel() {
private val _events = MutableSharedFlow()
val events = _events.asSharedFlow()
fun emitEvent(event: Event) {
viewModelScope.launch {
_events.emit(event)
}
}
}
sealed interface Event {
object RequestPermission : Event
}
@Composable
fun MyComposable(viewModel: MyViewModel) {
val permissionState = rememberPermissionState(...)
LaunchedEffect(viewModel) {
viewModel.events.collect {
when (it) {
is Event.RequestPermission -> {
permissionState.launchPermissionRequest()
}
}
}
}
Button(onClick = {
viewModel.emitEvent(Event.RequestPermission)
})
}
alaershov
05/26/2023, 3:13 PMalex009
05/26/2023, 3:18 PMmapState.value = MapState(
markers = listOf(Marker(image = ***, lat = **, long = **), **),
route = listOf(point1, point2, point3),
areas = listOf(...)
)
but this not implemented yet. it's just our experience after using of MapController from moko-maps in several projectsalaershov
05/26/2023, 3:24 PMmove()
methods that accept different paramerets. For such actions I use a similar approach - I share a Flow of events from VM to my Compose Map, and collect them inside Compose implementation. I'll definitely check out your approach to such things as movement with animation, setting a focus rectangle etc.alaershov
05/26/2023, 3:32 PMPablichjenkov
05/26/2023, 7:12 PMconsumed
boolean to track if the state was consumed on the composable side.
That or having a function in the ViewModel appears to be the recommended way.