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.
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.
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.
// 型を絞り込めば、そこから先は素の Google Maps SDK
val holder = mapViewState.getMapViewHolder()
if (holder is GoogleMapViewHolder) {
holder.map.isTrafficEnabled = true
}// AnyMapViewHolder から具象ホルダーへ絞り込む
if let holder = mapState.getMapViewHolder() as? MapKitViewHolder {
holder.mapView.showsTraffic = true
}// 共通インターフェースの map は unknown。プロバイダの型へ絞り込んでから使う
const holder = mapViewState.getMapViewHolder();
const map = holder?.map as maplibregl.Map | undefined;
map?.addLayer({ id: 'buildings', type: 'fill-extrusion', source: 'composite' });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.
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.
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.
// 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.