MapView lifecycle and events
A map is a view plus a state object. The state (mapViewState) is your handle on the camera, the design and every operation; the view just goes into the platform UI tree. Initialisation walks a shared set of stages, and load completion and user input arrive as events.
01 · mapViewState
mapViewState is the state object for one map. Implementations differ per provider, but the exposed members are the same, so your code only ever sees the shared interface.
The state outlives the view. When the view is rebuilt, camera position and design remain on the state, so the map comes back as it was.
// 状態は remember* で作る。画面回転をまたいでも同じ地図が復元される
val mapViewState =
rememberGoogleMapViewState(
mapDesign = GoogleMapDesign.Normal,
cameraPosition = initCameraPosition,
)
GoogleMapView(state = mapViewState, modifier = Modifier.fillMaxSize())// ObservableObject。@StateObject で View に持たせる
@StateObject private var mapLibreState = MapLibreViewState(
mapDesignType: MapLibreDesign.OsmBright,
cameraPosition: viewModel.initCameraPosition
)
MapLibreMapView(state: mapLibreState) { MapViewContent() }const [mapViewState, setMapViewState] =
useState<MapViewStateInterface<MapDesignTypeInterface<unknown>> | null>(null);
// MapViewContainer はサンプルアプリのプロバイダ切替ラッパー(SDK 同梱ではありません)
<MapViewContainer provider={provider} cameraPosition={INIT_CAMERA} onStateReady={setMapViewState}>
<Markers states={markerStates} />
</MapViewContainer>02 · Initialisation lifecycle
Every provider walks the same stages: load the SDK, create the view, create the map instance, finish drawing tiles. Because the stages are one shared InitState, you do not have to memorise per-provider rules about when the map is safe to touch.
Camera moves and overlay registration are accepted from MapCreated onward and queued internally. To wait for a fully drawn map, use onMapLoaded.
Surviving rotation and rebuilds
On Android the state is kept through rememberSaveable, so camera and design are restored after rotation or a configuration change; the map view itself is reused rather than destroyed.
When it is destroyed
On real teardown the controller releases its overlay controllers, tile-server routes and coroutine scope together. Switching providers cleans up the previous map through the same path.
var ready by remember { mutableStateOf(false) }
MapLibreMapView(
state = mapViewState,
// Called once, when the tiles have finished drawing
onMapLoaded = { state ->
ready = true
state.fitBounds(routeBounds, padding = 48)
},
) {
// Declarations here are not lost before MapCreated — they are queued internally
Marker(markerState)
}
if (!ready) {
// Your own loading overlay, if you want one. The map does not need blocking
Box(Modifier.fillMaxSize()) { CircularProgressIndicator() }
}03 · Events
The handlers you pass to the view are identical across the three platforms, and so are their types: load completion hands you the state object, taps a coordinate, camera changes a camera position.
GoogleMapView(
state = mapViewState,
onMapLoaded = { state -> viewModel.onMapLoaded(state) },
onMapClick = { point -> viewModel.onMapClick(point) },
onCameraMove = { camera -> viewModel.onCameraChanged(camera) },
onCameraMoveEnd = { camera -> viewModel.onCameraSettled(camera) },
) { /* markers, overlays */ }MapLibreMapView(
state: mapLibreState,
onMapLoaded: { state in viewModel.onMapLoaded(state) },
onCameraMoveStart: viewModel.onMapCameraMoveStart,
onCameraMove: viewModel.onCameraChanged,
onCameraMoveEnd: viewModel.onMapCameraMoveEnd
) {
MapViewContent()
}<MapViewContainer
provider={provider}
cameraPosition={INIT_CAMERA}
onMapClick={() => setSelected(null)}
onCameraMove={setCameraPosition}
>
<Markers states={markerStates} />
</MapViewContainer>Camera events also update cameraPosition on the state, so the latest camera is readable even without a handler. To avoid refetching mid-gesture, use only onCameraMoveEnd.
04 · The escape hatch to native
When the shared API is not enough, there is a way down to the native map instance. You rarely need it — it exists for that one provider-specific feature.
A holder around the native map
The holder carries the platform view and the map instance. SDK-specific code stays behind it and never leaks into shared code.
Coordinate and screen conversion
Convert between geographic coordinates and screen pixels — needed when you overlay your own UI on the map.
// どうしてもネイティブAPIが必要なときだけ取り出す val holder = mapViewState.getMapViewHolder() val nativeMap = holder?.map // GoogleMap / MapLibreMap / ... val offset = holder?.toScreenOffset(point)
if let holder = mapViewState.getMapViewHolder() {
let nativeMap = holder.map
let offset = holder.toScreenOffset(position: point)
}const holder = mapViewState.getMapViewHolder(); const nativeMap = holder?.map; // google.maps.Map / maplibregl.Map / ...
Code that uses the holder is provider-specific. Keep a clear line between what stays shared and where you deliberately use a native API.