Reputation: 460
I am trying to build an app with navigation to multiple different screens (using Bottom Navigation).
One of the screens, the startDestination
, is a Google Maps view, looking at Official compose example: Crane to get it to work, and it does.
However, when navigating to another screen, and back, the MapView
gets recomposed and is slowly loading back in. We start back at the initial camera position, zoom level, and so on. There probably is a way to remember
and re-apply those attributes, but I am more interested in keeping the complete state of the Google Maps view intact. (Looking at the current Google Maps app, for Android, it does exactly what I'm looking for, eventhough they aren't using Jetpack Compose)
Is there a way to achieve this?
I already remember
the MapView
@Composable
fun rememberMapViewWithLifecycle(): MapView {
val context = LocalContext.current
val mapView = remember {
MapView(context).apply {
id = R.id.map
}
}
val lifecycle = LocalLifecycleOwner.current.lifecycle
DisposableEffect(lifecycle, mapView) {
// Make MapView follow the current lifecycle
val lifecycleObserver = getMapLifecycleObserver(mapView)
lifecycle.addObserver(lifecycleObserver)
onDispose {
lifecycle.removeObserver(lifecycleObserver)
}
}
return mapView
}
private fun getMapLifecycleObserver(mapView: MapView): LifecycleEventObserver =
LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_CREATE -> mapView.onCreate(Bundle())
Lifecycle.Event.ON_START -> mapView.onStart()
Lifecycle.Event.ON_RESUME -> mapView.onResume()
Lifecycle.Event.ON_PAUSE -> mapView.onPause()
Lifecycle.Event.ON_STOP -> mapView.onStop()
Lifecycle.Event.ON_DESTROY -> mapView.onDestroy()
else -> throw IllegalStateException()
}
}
To give more context, the MapView
is in the top of this screen
@ExperimentalMaterialApi
@Composable
fun MapsScreen(
modifier: Modifier = Modifier,
viewModel: EventViewModel = viewModel()
) {
...
val mapView = rememberMapViewWithLifecycle()
MapsScreenView(modifier, uiState, mapView)
}
A completely different approach I tried, is through a BackdropScaffold
(in a Scaffold
because I want the BottomBar
..) where backLayerContent
is the MapsScreen()
and the frontLayerContent
are the other screens, the "front" is configured so it will cover the entire screen when active. It feels really ugly and horrible, but it kinda works..
Scaffold(
topBar = {
SmallTopAppBar(
title = { Text(text = "Test") },
)
},
bottomBar = {
BottomBar(navController) { screen ->
showMaps = screen == Screen.MainMaps
coroutineScope.launch {
if (showMaps) scaffoldState.reveal() else scaffoldState.conceal()
}
}
},
content = { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
BackdropScaffold(
scaffoldState = scaffoldState,
appBar = { },
frontLayerContent = {
EventsScreen()
},
backLayerContent = {
MapsScreen()
},
peekHeight = if (showMaps) 300.dp else 0.dp,
headerHeight = if (showMaps) BackdropScaffoldDefaults.HeaderHeight else 0.dp,
gesturesEnabled = showMaps
)
}
}
)
Did anyone have this same problem and found an actual solution? (We really need Jetpack Compose support for this I guess, intead of the AndroidView
approach)
Upvotes: 9
Views: 3075
Reputation: 2840
Google just publish a library to handle the MapView state Android-Maps-Compose
val cameraPositionState: CameraPositionState = rememberCameraPositionState()
GoogleMap(cameraPositionState = cameraPositionState)
Button(onClick = { cameraPositionState.move(CameraUpdateFactory.zoomIn()) }) {
Text(text = "Zoom In")
}
Upvotes: 3