Jetpack Compose で構築した画面に Google Map を配置する

## はじめに

- Maps SDK for Android の MapView を使って Jetpack Compose で構成した画面に Google Map を表示する方法です
- 今後使うかも知れないので、ここに残しておくことにしました。どなたかのお役に立てば嬉しいです
- ちなみに元ネタは下記の Codelab です。気になった方はトライしてみてください
    - [Jetpack Compose の高度な状態と副作用](https://developer.android.com/codelabs/jetpack-compose-advanced-state-side-effects)
- 従来の Android View で `MapView` を使うやり方については、以前[こちらの記事](https://qiita.com/outerlet/items/36a9152c1cce46a9cfa6)で共有させて頂きました
    - かなり前の記事ですが、基本的には今でも同じ方法が使えるようです

## 実装

ポイントは以下の2点になるかと思います

- ライフサイクルと同期した `MapView` を生成し保持する
- コンポーザブルで `MapView` を 配置する

### ライフサイクルと同期した MapView を生成し保持する

- [以前の記事](https://qiita.com/outerlet/items/36a9152c1cce46a9cfa6#2-fragment-%E3%81%AE%E4%BD%9C%E6%88%90) にも書きましたが、 `MapView` は `Activity` や `Fragment` のライフサイクルと同期し、イベントに応じて適切なメソッドが呼び出されなくてはなりません
    - `Activity.onCreate()` の中で `MapView.onCreate()` が呼ばれなくてはならない、といった具合
    - [MapView のリファレンス](https://developers.google.com/android/reference/com/google/android/gms/maps/MapView) にもその旨の記述 （『You must call this method from the parent Activity/Fragment's corresponding method』）があります
- ライフサイクルとの同期は、 [LifecycleEventObserver](https://developer.android.com/reference/kotlin/androidx/lifecycle/LifecycleEventObserver) と `DisposableEffect` を組み合わせて実現するのがよさそうです
    - `DisposableEffect` については[こちら](https://developer.android.com/develop/ui/compose/side-effects?hl=ja#disposableeffect)が詳しいです

#### サンプルコード

`MapView` を保持／参照するためのコンポーザブルが以下です
（ほぼ元ネタそのままなので、必要に応じて手を入れるなどして頂ければと思います）

```kotlin
@Composable
fun rememberMapViewWithLifecycle(): MapView {
    val context = LocalContext.current
    val mapView = remember {
        MapView(context).apply { id = R.id.map_view_id }
    }

    val lifeCycle = LocalLifecycleOwner.current.lifecycle

    DisposableEffect(key1 = lifeCycle, key2 = mapView) {
        val lifecycleObserver = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_CREATE -> mapView.onCreate(Bundle())
                Lifecycle.Event.ON_START -> mapView.onStart()
                Lifecycle.Event.ON_RESUME -> mapView.onResume()
                Lifecycle.Event.ON_PAUSE -> mapView.onPause()
                Lifecycle.Event.ON_STOP -> mapView.onStop()
                Lifecycle.Event.ON_DESTROY -> mapView.onDestroy()
                else -> throw IllegalStateException()
            }
        }

        lifeCycle.addObserver(lifecycleObserver)

        onDispose {
            lifeCycle.removeObserver(lifecycleObserver)
        }
    }

    return mapView
}
```

### コンポーザブルで MapView を 配置する

- `MapView` が取得できれば、[AndroidView コンポーザブル](https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary#AndroidView(kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1)) を使って配置できます

#### サンプルコード

JR大阪駅にマーカーを立てて、 16.0f のズーム率でマップを表示したものです

```kotlin
val mapView = rememberMapViewWithLifecycle()

AndroidView(
    modifier = Modifier.fillMaxSize(),
    factory = { mapView },
) { view ->
    view.getMapAsync {
        val position = LatLng(34.702473, 135.495926)
        val zoom = 16.0f

        it.addMarker(
            MarkerOptions().position(position).title("Sample Marker")
        )

        it.moveCamera(
            CameraUpdateFactory.newCameraPosition(
                CameraPosition.Builder().apply {
                    target(position)
                    zoom(zoom)
                }.build()
            )
        )
    }
}
```

#### 実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67520/e9d5a0d9-6ad8-79f3-6043-13562f97c45b.png" width="240" />

## 追記

- [自分なりに Compose + MapView の実装を試みたもの](https://github.com/outerlet/StationInfoApp) を GitHub で公開しております
- このエントリに記載したこと含め、何かしらの誤りや改善点などあれば指摘頂けますと有難いです