MapViewHolder
MapViewHolder は、MapConductor の統一 API でカバーされていない特定の地図SDK機能が必要な高度なユースケースのために、ネイティブ地図 SDK インスタンスへのアクセスを提供します。MapConductor は共通のインターフェースを提供しますが、すべてのネイティブ機能を完全にラップするわけではなく、MapViewHolder がこのギャップを埋めます。
MapConductor は地図SDK間で統一された API を提供することを目指していますが、完全な機能パリティは必ずしも可能ではありません。MapViewHolder を使用すると、開発者は地図SDK固有の機能が必要な場合に、基盤となるネイティブ地図インスタンスにアクセスできます。
interface MapViewHolder<ActualMapViewType, ActualMapType> { val mapView: ActualMapViewType val map: ActualMapType}MapViewHolder へのアクセス
Section titled “MapViewHolder へのアクセス”各地図SDKの MapViewState 実装は、その特定の MapViewHolder へのアクセスを提供します:
// Google Mapsval googleMapState = GoogleMapViewStateImpl()val googleHolder: GoogleMapViewHolder? = googleMapState.getMapViewHolder()
// Mapboxval mapboxState = MapboxViewStateImpl()val mapboxHolder: MapboxMapViewHolder? = mapboxState.getMapViewHolder()
// HERE Mapsval hereState = HereViewStateImpl()val hereHolder: HereViewHolder? = hereState.getMapViewHolder()
// ArcGISval arcgisState = ArcGISMapViewStateImpl()val arcgisHolder: ArcGISMapViewHolder? = arcgisState.getMapViewHolder()地図SDK固有の実装
Section titled “地図SDK固有の実装”Google Maps
Section titled “Google Maps”typealias GoogleMapViewHolder = MapViewHolder<MapView, GoogleMap>
// ネイティブ Google Maps API にアクセスgoogleHolder?.let { holder -> val nativeMapView: MapView = holder.mapView val googleMap: GoogleMap = holder.map
// Google Maps 固有の機能を使用 googleMap.setMapStyle(MapStyleOptions.loadRawResourceStyle(context, R.raw.style_json)) googleMap.setOnPoiClickListener { poi -> // 関心点のクリックを処理 }
nativeMapView.onResume() nativeMapView.onPause()}Mapbox
Section titled “Mapbox”typealias MapboxMapViewHolder = MapViewHolder<MapView, MapboxMap>
// ネイティブ Mapbox API にアクセスmapboxHolder?.let { holder -> val mapboxMapView: MapView = holder.mapView val mapboxMap: MapboxMap = holder.map
// Mapbox 固有の機能を使用 mapboxMap.getStyle { style -> style.addSource(GeoJsonSource("custom-source", geoJsonData)) style.addLayer(FillLayer("custom-layer", "custom-source")) }
// Mapbox プラグインにアクセス val locationComponent = mapboxMap.locationComponent locationComponent.activateLocationComponent(context)}HERE Maps
Section titled “HERE Maps”typealias HereViewHolder = MapViewHolder<MapView, MapScene>
// ネイティブ HERE SDK API にアクセスhereHolder?.let { holder -> val hereMapView: MapView = holder.mapView val mapScene: MapScene = holder.map
// HERE 固有の機能を使用 val searchEngine = SearchEngine() val textQuery = TextQuery("coffee shops", geoCoordinates)
searchEngine.search(textQuery) { searchError, searchResults -> // HERE 検索結果を処理 }
// HERE ルーティング val routingEngine = RoutingEngine() // ルーティングを設定して使用}ArcGIS
Section titled “ArcGIS”typealias ArcGISMapViewHolder = MapViewHolder<WrapSceneView, SceneView>
// ネイティブ ArcGIS API にアクセスarcgisHolder?.let { holder -> val wrapSceneView: WrapSceneView = holder.mapView val sceneView: SceneView = holder.map
// ArcGIS 固有の機能を使用 val portal = Portal("https://www.arcgis.com/") val portalItem = PortalItem(portal, "your-webmap-id") val webMap = ArcGISMap(portalItem)
sceneView.map = webMap
// ArcGIS 分析ツール val serviceArea = ServiceAreaTask("https://route-api.arcgis.com/...") // 分析を設定して使用}一般的なユースケース
Section titled “一般的なユースケース”カスタムスタイリング
Section titled “カスタムスタイリング”@Composablefun CustomStyledMap() { val mapViewState = rememberGoogleMapViewState()
LaunchedEffect(mapViewState) { // 地図の初期化を待つ delay(1000)
mapViewState.getMapViewHolder()?.let { holder -> val googleMap = holder.map
// カスタム地図スタイルを適用 val styleJson = loadStyleFromAssets(context, "custom_style.json") googleMap.setMapStyle(MapStyleOptions(styleJson))
// 地図 UI を設定 googleMap.uiSettings.isMyLocationButtonEnabled = false googleMap.uiSettings.isCompassEnabled = true } }
// MapView を GoogleMapView、MapboxMapView などの選択した地図SDKに置き換えてください GoogleMapView(state = mapViewState) { // MapConductor コンポーネント }}@Composablefun AnalyticsIntegration() { val mapViewState = remember { MapboxViewStateImpl() }
LaunchedEffect(mapViewState) { mapViewState.getMapViewHolder()?.let { holder -> val mapboxMap = holder.map
// カスタム地図イベントを追跡 mapboxMap.addOnMapClickListener { point -> Analytics.track("map_click", mapOf( "lat" to point.latitude, "lng" to point.longitude, "zoom" to mapboxMap.cameraPosition.zoom )) true }
// スタイル変更を追跡 mapboxMap.addOnStyleLoadedListener { Analytics.track("style_loaded", mapOf( "style_id" to mapboxMap.style?.styleURI )) } } }
MapboxMapView(state = mapViewState) { // MapConductor コンポーネント }}パフォーマンス最適化
Section titled “パフォーマンス最適化”@Composablefun PerformanceOptimizedMap() { val mapViewState = remember { HereViewStateImpl() }
LaunchedEffect(mapViewState) { mapViewState.getMapViewHolder()?.let { holder -> val mapScene = holder.map
// HERE 固有のパフォーマンス設定 mapScene.setLayerVisibility(MapScene.Layers.TRAFFIC_FLOW, VisibilityState.VISIBLE)
// 特定のユースケースのために最適化 val mapSettings = mapScene.mapSettings mapSettings.isTiltGesturesEnabled = false mapSettings.isRotateGesturesEnabled = false
// カスタム詳細レベル設定 mapScene.setLevelOfDetail(LevelOfDetail.HIGH) } }
HereMapView(state = mapViewState) { // MapConductor コンポーネント }}サードパーティサービスとの統合
Section titled “サードパーティサービスとの統合”@Composablefun ThirdPartyIntegration() { val mapViewState = rememberArcGISMapViewState()
LaunchedEffect(mapViewState) { mapViewState.getMapViewHolder()?.let { holder -> val sceneView = holder.map
// ArcGIS Online サービスと統合 val featureTable = ServiceFeatureTable("https://services.arcgis.com/...") val featureLayer = FeatureLayer(featureTable)
sceneView.map.basemap.baseLayers.add(featureLayer)
// フィーチャーをクエリ val queryParams = QueryParameters().apply { whereClause = "STATE_NAME = 'California'" }
featureTable.queryFeaturesAsync(queryParams).addDoneListener { val results = it.result // フィーチャークエリ結果を処理 } } }
ArcGISMapView(state = mapViewState) { // MapConductor コンポーネント }}ライフサイクル管理
Section titled “ライフサイクル管理”地図ライフサイクルイベント
Section titled “地図ライフサイクルイベント”@Composablefun LifecycleAwareMap() { val mapViewState = rememberGoogleMapViewState() val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> mapViewState.getMapViewHolder()?.let { holder -> val mapView = holder.mapView
when (event) { Lifecycle.Event.ON_RESUME -> mapView.onResume() Lifecycle.Event.ON_PAUSE -> mapView.onPause() Lifecycle.Event.ON_START -> mapView.onStart() Lifecycle.Event.ON_STOP -> mapView.onStop() Lifecycle.Event.ON_DESTROY -> mapView.onDestroy() else -> { } } } }
lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } }
GoogleMapView(state = mapViewState) { // MapConductor コンポーネント }}エラー処理と安全性
Section titled “エラー処理と安全性”Null 安全性
Section titled “Null 安全性”fun accessNativeMap(mapViewState: GoogleMapViewStateImpl) { val holder = mapViewState.getMapViewHolder()
if (holder != null) { // ネイティブ API に安全にアクセス val googleMap = holder.map val mapView = holder.mapView
// ネイティブ機能を使用 googleMap.setOnMarkerClickListener { marker -> // マーカークリックを処理 true } } else { // 地図がまだ初期化されていないか利用できない Log.w("MapAccess", "MapViewHolder が利用できません") }}初期化タイミング
Section titled “初期化タイミング”@Composablefun SafeNativeAccess() { val mapViewState = remember { MapboxViewStateImpl() } var isMapReady by remember { mutableStateOf(false) }
LaunchedEffect(mapViewState) { // 地図が準備できるまでポーリング while (!isMapReady) { delay(100) isMapReady = mapViewState.getMapViewHolder() != null }
// ネイティブ API を使用するのが安全 mapViewState.getMapViewHolder()?.let { holder -> val mapboxMap = holder.map // ネイティブ機能を設定 } }
MapboxMapView(state = mapViewState) { // MapConductor コンポーネント }}ベストプラクティス
Section titled “ベストプラクティス”- 初期化チェック: ネイティブ API にアクセスする前に、
getMapViewHolder()が非 null を返すことを常に確認 - ライフサイクル認識: ネイティブ API を使用する際は、地図のライフサイクルイベントを適切に処理
- エラー処理: 地図SDK API が例外をスローする可能性があるため、ネイティブ API 呼び出しを try-catch ブロックでラップ
- ドキュメント: ネイティブ API の使用については、各地図SDKの公式ドキュメントを参照
- テスト: ネイティブ API を使用する際は、すべてのターゲット地図SDKで徹底的にテスト
- フォールバック: ネイティブ機能が利用できない場合のフォールバック動作を提供
- バージョン互換性: ネイティブ API の使用が、ターゲットとしている SDK バージョンと互換性があることを確認
制限と考慮事項
Section titled “制限と考慮事項”- プラットフォーム依存性: ネイティブ API の使用は、特定の地図SDKにコードを結び付けます
- メンテナンスオーバーヘッド: 地図SDK API の変更により、ネイティブ API の使用を更新する必要があります
- テストの複雑性: 地図SDK固有のコードパスをカバーするため、より複雑なテストが必要
- 機能パリティ: すべての地図SDKが同等のネイティブ機能をサポートしているわけではありません
- MapConductor 統合: ネイティブの変更は MapConductor の状態管理と統合されない可能性があります
MapViewHolder は高度なユースケースのための強力なエスケープハッチですが、MapConductor の統一 API アプローチの利点を維持するために慎重に使用する必要があります。