Docs / Map view / MapView & state

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.

ANDROID
MapViewStateInterface
iOS
MapViewStateProtocol
REACT
MapViewStateInterface
Platform

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.

Member
Description
id
Identifier for the state; keeps pointing at the same map across view rebuilds.
cameraPosition
The current camera position, including the visible region.
mapDesignType
The current map design; assigning switches it immediately.
moveCameraTo()
Moves the camera, animating when given a duration.
fitBounds()
Fits the camera to a bounding box. A method on the state object on all three platforms, delegating internally to the controller.
getMapViewHolder()
Returns the holder wrapping the native map instance.
MapViewState · Jetpack Compose
// 状態は remember* で作る。画面回転をまたいでも同じ地図が復元される
val mapViewState =
    rememberGoogleMapViewState(
        mapDesign = GoogleMapDesign.Normal,
        cameraPosition = initCameraPosition,
    )

GoogleMapView(state = mapViewState, modifier = Modifier.fillMaxSize())

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.

Figure · how InitState progresses
NotStarted
Nothing has started yet.
Initializing
Initialisation begins — key validation and SDK loading.
SdkInitialized
The map SDK itself has finished loading.
MapViewCreated
The platform view (MapView / UIView / DOM element) is created.
MapCreating
The map instance is being created inside the view.
MapCreated
The map instance is usable; camera moves and overlays start applying.
MapLoaded
Tiles are drawn. onMapLoaded fires at this stage.
Failed
Initialisation failed — bad key, no network, and so on.
The stages are declared as the same enumeration on Android, iOS and React, so what you wait for does not change when you swap providers.
01 sdkInitialize
Load the map SDK — a script tag on the web, platform init on mobile. Failure moves to Failed.
02 createHolder
Wrap the native map instance in a holder; SDK-specific types stay behind it.
03 createController
Wrap the holder in a controller that exposes the shared API and wires events, then attach it to the state.
RESTORE

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.

TEARDOWN

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.

What the video shows · Android + MapLibreKotlin · Jetpack Compose
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() }
}
Sample video · initialisation proceeding
Video not shot yetInitState advancing stage by stage from map creation to the point it accepts input. Seeing when the loading state hands over is what makes it concrete.

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.

Event
Payload
Description
onMapLoaded
MapViewState
The map has loaded. Usually you keep the state object handed to you here.
onMapClick
GeoPoint
A tap on the map — closing bubbles, dropping pins.
onMapLongClick
GeoPoint
A long press.
onCameraMoveStart
MapCameraPosition
A camera move begins — for user gestures and programmatic moves alike.
onCameraMove
MapCameraPosition
Fires continuously during the move; good for live readouts.
onCameraMoveEnd
MapCameraPosition
The move settled. Refetch data here.
GoogleMapView · Receiving events
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 */ }

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.

MapViewHolder

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.

toScreenOffset / fromScreenOffset

Coordinate and screen conversion

Convert between geographic coordinates and screen pixels — needed when you overlay your own UI on the map.

Getting the holder
// どうしてもネイティブAPIが必要なときだけ取り出す
val holder = mapViewState.getMapViewHolder()
val nativeMap = holder?.map // GoogleMap / MapLibreMap / ...
val offset = holder?.toScreenOffset(point)

Code that uses the holder is provider-specific. Keep a clear line between what stays shared and where you deliberately use a native API.

Related pages