Docs / More / Native extensions

Native extensions

MapConductor does not wrap every feature of every map SDK. When you need something the unified API does not model, there are two ways down that do not cost you the abstraction — the rest of the screen keeps running on the shared API.

1 · HOLDER
Android · iOS · React
2 · REGISTRY
Android · iOS · React

01 · Where abstraction stops

The unified API covers concepts that exist everywhere. Extruded 3D buildings, one vendor traffic layer, a bespoke shader — those are deliberately out of scope. Wrapping everything would turn the shared API into a lowest common multiple and make it unpleasant to use.

1 · MapViewHolder
Borrow the real object
Pull out the native map instance and call that SDK directly. Available on all three platforms.
2 · MapServiceRegistry
Inject a capability
An add-on module registers its capability under a typed key without modifying Core. This is the mechanism for extending the SDK itself.
Platform

02 · Reaching the native map

getMapViewHolder() on the state object returns a holder carrying that provider view and map instance. Narrow the type and you are on the raw SDK. The holder also does coordinate ⇄ screen-pixel conversion.

Member
Description
mapView
The provider view — MKMapView, MapView, the maplibregl container and so on.
map
The map object itself; this is what you call the SDK on.
toScreenOffset(position)
Coordinate to screen pixels, accounting for tilt and bearing.
fromScreenOffset(offset)
Screen pixels back to a coordinate; async and sync variants.
Kotlin
// 型を絞り込めば、そこから先は素の Google Maps SDK
val holder = mapViewState.getMapViewHolder()
if (holder is GoogleMapViewHolder) {
    holder.map.isTrafficEnabled = true
}

The holder only exists once the map has initialised — it returns null before that, so call it inside onMapLoaded or later.

03 · Plugging in a module

MapServiceRegistry is the seam an add-on module uses to inject its capability without modifying Core: register under a typed key, retrieve with the same key, and because the key carries the type nothing needs casting on the way out. Marker clustering is what uses it today — it resolves the MarkerRenderingSupport its provider registered and renders through that.

Android · React

MapServiceRegistry

Define a MapServiceKey<T> as a singleton, put it into the MutableMapServiceRegistry, and get it back where needed. On Android the map-scoped registry is reached through the LocalMapServiceRegistry composition local.

iOS

MapServiceRegistryScope

Keys are types conforming to MapServiceKey, put into a MutableMapServiceRegistry. The registry lives on the state object (MapViewState.serviceRegistry) and the provider makes it visible with MapServiceRegistryScope.with(...) for exactly as long as content is being assembled. It is not a SwiftUI Environment value because map content is a MapViewContent value, not a view hierarchy.

map/MapServiceRegistry · the typed key
// Kotlin: キーはシングルトンの object として定義する
object MarkerRenderingSupportKey : MapServiceKey<MarkerRenderingSupport<*>>

registry.put(MarkerRenderingSupportKey, support)
val support = registry.get(MarkerRenderingSupportKey) // 型が付いたまま返る

// Swift: キーは MapServiceKey に準拠した型として定義する
enum MarkerRenderingSupportKey: MapServiceKey {
    typealias Value = any MarkerRenderingSupport
}

registry.put(MarkerRenderingSupportKey.self, support)
let support = registry.get(MarkerRenderingSupportKey.self)

// TypeScript: createMapServiceKey<T>() でキーを作る
const MarkerRenderingSupportKey = createMapServiceKey<MarkerRenderingSupport>();

All three of these bring provider-specific code into your screen — the part that stops working when you swap providers. Keep the places you use them collected behind one branch.

Related pages