Google Maps Implementation
This page covers the Google Maps provider implementation. For the shared architecture, provider abstraction, and marker system, see the Maps overview.
GoogleMapContentβ
GoogleMapContent uses the maps-compose library with MarkerComposable for Compose-native markers:
// ui/maps/GoogleMapContent.kt
GoogleMap(
cameraPositionState = cameraPositionState,
properties = MapProperties(
mapType = mapType,
isMyLocationEnabled = locationEnabled,
mapStyleOptions = mapStyleOptions, // Dark theme JSON style
),
uiSettings = MapUiSettings(
zoomControlsEnabled = false,
myLocationButtonEnabled = false,
compassEnabled = false,
mapToolbarEnabled = false,
),
) {
devices.forEach { deviceWithPosition ->
MarkerComposable(
keys = arrayOf(device.id, device.status, lat, lng, speed, course, isSelected),
state = MarkerState(position = LatLng(lat, lng)),
onClick = { onDeviceClick(deviceWithPosition); false },
) {
DeviceMarker(
variant = MarkerVariant.DEVICE,
device = device,
position = position,
isSelected = isSelected,
)
}
}
}
Key behaviorsβ
- Dark theme: Loads
R.raw.google_map_dark_styleasMapStyleOptionswhenVislaTheme.isDarkThemeis true - Camera reporting:
LaunchedEffect(cameraPositionState.position)maps the GoogleCameraPositiontoMapCameraState - Fit-all logic: Uses
LatLngBounds.builder()with smart detection β if all devices already fit in the visible region, it only pans to center without changing zoom - Projection: Exposes
cameraPositionState.projection.toScreenLocation()for edge-clamped markers
Fit-All Algorithmβ
private suspend fun fitAllDevices(devices: List<DeviceWithPosition>, cameraPositionState: CameraPositionState, forceZoom: Boolean = false) {
val positions = devices.mapNotNull { it.position }.filter { it.hasValidCoordinates }
if (positions.size >= 2) {
val bounds = LatLngBounds.builder().apply {
positions.forEach { include(LatLng(it.latitude!!, it.longitude!!)) }
}.build()
val visibleBounds = cameraPositionState.projection?.visibleRegion?.latLngBounds
val alreadyFits = !forceZoom && visibleBounds != null &&
visibleBounds.contains(bounds.northeast) &&
visibleBounds.contains(bounds.southwest)
if (alreadyFits) {
cameraPositionState.animate(CameraUpdateFactory.newLatLng(bounds.center))
} else {
cameraPositionState.animate(
CameraUpdateFactory.newLatLngBounds(bounds, MapConstants.FIT_ALL_PADDING_INT),
)
}
} else if (positions.size == 1) {
cameraPositionState.animate(
CameraUpdateFactory.newLatLngZoom(/* ... */, MapConstants.FOCUS_ZOOM_FLOAT),
)
}
}
Comparison with Mapboxβ
For a side-by-side comparison of Google Maps and Mapbox features, see the Mapbox implementation.