AuStrike
AuStrike

Reputation: 460

Keeping the Google Maps state in Jetpack Compose

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

Answers (1)

Neige
Neige

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

Related Questions