Google Map
Loading Spinner
Google map takes a little while to initialize. While it's loading, the user sees a blank screen, which may make them think the application isn't working.
Adding an animated progress indicator lets the user know the application isn't just hanging. We'll add an "indeterminate" (no obvious beginning/end/length) spinning indicator on top of the map for this.
We need state to keep track of whether the map has finished loading. A simple remembered MutableState
will do the trick. This drives the display of the CircularProgressIndicator
.
Note
Displaying or not displaying the progress indicator just takes an if
expression. Note that we could have passed !mapLoaded
for the visible
parameter to AnimatedVisibility
, but that means we'd still be looking at that Composable on every recomposition. Instead I chose to add the if
expression around it.
This likely doesn't make a big difference here, but I wanted to demonstrate how normal Kotlin logic can be used to conditionally display overlays like this. Some overlays that you use (like a dialog) won't have a parameter like visible
.
AnimatedVisibility
is used here to fade-out the progress spinner when it's time to leave the composition. Because it's after the GoogleMap
in the Box
, it appears on top of it. The solid background prevents any part of the map from being visible before it's completely loaded.
When the map tells us it's loaded, we set mapLoaded
. When its value changes, recomposition is triggered and changes whether or not we include the progress indicator in the composition tree.
Note
Did you notice how we changed where the passed-in Modifier
is used? We moved it from the GoogleMap
to the to the Box
. This function declares the parent of the GoogleMap
(the Box
), so we have full control over its Modifiers
.
Code Changes
CHANGED: /app/src/main/java/com/androidbyexample/googlemap/MainActivity.kt
package com.androidbyexample.googlemap import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.animation.AnimatedVisibilityimport androidx.compose.animation.EnterTransitionimport androidx.compose.animation.fadeOutimport androidx.compose.foundation.backgroundimport androidx.compose.foundation.layout.Boximport androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.wrapContentSizeimport androidx.compose.material3.CircularProgressIndicatorimport androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.ui.Modifier import com.androidbyexample.googlemap.ui.theme.GoogleMapTheme import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng import com.google.maps.android.compose.CameraPositionState import com.google.maps.android.compose.GoogleMap import com.google.maps.android.compose.MarkerInfoWindowContent import com.google.maps.android.compose.rememberCameraPositionState import com.google.maps.android.compose.rememberMarkerState private val googleHQ = LatLng(37.42423291057923, -122.08811454627153)private val defaultCameraPosition = CameraPosition.fromLatLngZoom(googleHQ, 11f)class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { GoogleMapTheme { // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) {val cameraPositionState = rememberCameraPositionState { position = defaultCameraPosition }GoogleMapDisplay( place = googleHQ, placeDescription = "Google HQ", cameraPositionState = cameraPositionState, modifier = Modifier.fillMaxSize(), )} } } } }@Composable fun GoogleMapDisplay( place: LatLng, placeDescription: String, cameraPositionState: CameraPositionState, modifier: Modifier, ) {var mapLoaded by remember { mutableStateOf(false) }val placeState = rememberMarkerState(key = place.toString(), position = place)Box(modifier = modifier,) { } GoogleMap(cameraPositionState = cameraPositionState,onMapLoaded = { mapLoaded = true },) {// modifier = modifier,modifier = Modifier.fillMaxSize(),MarkerInfoWindowContent( state = placeState, title = placeDescription, onClick = { placeState.showInfoWindow() true } )}if (!mapLoaded) { AnimatedVisibility( visible = true, modifier = Modifier.fillMaxSize(), enter = EnterTransition.None, exit = fadeOut() ) { CircularProgressIndicator( modifier = Modifier .background(MaterialTheme.colorScheme.background) .wrapContentSize() ) } }}