[備忘録] Androidアプリに Google Map を追加する

## 前提
これをやったんだ

https://developers.google.com/codelabs/maps-platform/maps-platform-101-android?hl=ja

## 大まかな手順
1. Google Maps Platfrom にて **API Key** と **Map ID** を設定する 
2. Viewファイルを作成
3. 処理部分の記述

## Google Maps Platform にて **API Key** と **Map ID** を設定する 
### API Keyの設定
こちらを参考に設定した。

https://developers.google.com/maps/gmp-get-started?hl=ja#create-project

https://www.youtube.com/watch?v=n1UorU1PALk&ab_channel=GoogleMapsPlatform

API Key をViewファイルなどにハードコードで書き込むとセキュリティ上問題があるため、今回はAndroidプロジェクト内に存在する`local.properties`というファイルに書き込んた。

### Map IDの設定
Map ID を用いると地図のスタイルを変更することができる。
ちなみに 2023/08/18 時点で Android の　Map ID はまだサポートされていないため、**別に意味はない**。

こちらを参考に設定した。
https://developers.google.com/maps/documentation/get-map-id?hl=ja

### local.propertiesとは
デフォルトで`.gitignore`されているのでAPI Keyなど公開したくない定数を定義できるファイル。
Cloneする度に設定する必要があるので注意。
またGitHub Actionsを利用する際にもsecretsで別途対応する必要があるため注意。

今回はこんな感じで設定する
```properties
GOOGLE_MAPS_API_KEY=****************************
GOOGLE_MAP_ID=*********
```
以下参考

https://developer.android.com/studio/build?hl=ja#properties-files

> 以下の項目など、ビルドシステムのローカル環境プロパティを構成します。
> - ndk.dir - NDK へのパス。このプロパティは非推奨になりました。ダウンロードしたバージョンの NDK はすべて、Android SDK ディレクトリ内の ndk ディレクトリにインストールされています。
> - sdk.dir - SDK へのパス。
> - cmake.dir - CMake へのパス。
> - ndk.symlinkdir - Android Studio 3.5 以降では、インストールされている NDK のパスよりも短くできる、NDK へのシンボリック リンクが作成されます。


## Viewファイルを作成
２つのViewファイルを作成した。
- activity_main.xml
    - Google Mapを表示するメイン画面
- marker_info_contents.xml
    - マーカーをタップした際に表示されるポップ!

<img width="300" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415127/0b849775-fc5b-95d1-6e9b-3e101be29055.png">

### activity_main.xml
```xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:map="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/map_fragment"
        class="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        map:mapId="${GOOGLE_MAP_ID}" />

</FrameLayout>
```
#### コードの解説

- `xmlns:map="http://schemas.android.com/apk/res-auto"`
maps のカスタム XML 属性を使用するための宣言。


- `class="com.google.android.gms.maps.SupportMapFragment"`
必要なライフサイクルのニーズを自動的に処理する、マップのViewのWrapper。
こいつを使うと魔法のように簡単に Google Map を表示できる。
詳細はこちら。

https://developers.google.com/android/reference/com/google/android/gms/maps/SupportMapFragment


### marker_info_contents.xml
```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:padding="8dp">

    <TextView
        android:id="@+id/text_view_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/black"
        android:textSize="18sp"
        android:textStyle="bold"
        tools:text="Title"/>

    <TextView
        android:id="@+id/text_view_address"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/black"
        android:textSize="16sp"
        tools:text="123 Main Street"/>

    <TextView
        android:id="@+id/text_view_rating"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/black"
        android:textSize="16sp"
        tools:text="Rating: 3"/>

</LinearLayout>
```
特殊なことは特にない。

## 処理部分の記述
構成はこんな感じだった。
```zshsh
app/src/main/java/com/google/codelabs/buildyourfirstmap
├── BitmapHelper.kt
├── MainActivity.kt
├── MarkerInfoWindowAdapter.kt
└── place
    ├── Place.kt
    ├── PlaceRenderer.kt
    ├── PlacesReader.kt
    └── PlacesResponse.kt
```
### 各ファイルの概要
| 名前 | 詳細 |
| --- | --- |
| MainActivity.kt | メインアクティビティー　|
| BitmapHelper.kt | Drawableファイルをマーカーアイコンとして使用するために、BitmapDescriptor に変換するためのヘルパー　|
| MarkerInfoWindowAdapter | マーカーをタップした際のウィンドウに関する処理をする |

| 名前 | 詳細 |
| --- | --- |
| Place.kt | 地図上ある一点の情報に関するDataClass。マーカーのDataClassだと思って良い |
| PlacesReader.kt | ファイル places.json から場所 JSON オブジェクトのリストを読み取る。places.jsonは用意されたもので、マーカーとして表示したい場所が記述されている。 |
|  PlaceRenderer.kt | Place オブジェクト用のカスタムClusterRenderer。マーカーに関する処理を記述する |
| PlacesResponse.kt | places.json からデータを受け取るためのDataClass |

### 各ファイルの詳細
#### MainActivity.kt
```kotlin
....

class MainActivity : AppCompatActivity() {
    
    // 場所（マーカー）のリスト
    private val places: List<Place> by lazy {
        PlacesReader(this).read()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val mapFragment =
            supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment
        lifecycleScope.launchWhenCreated {
            val googleMap = mapFragment.awaitMap()  // 地図を取得する
            googleMap.awaitMapLoad()  // マップの読み込みが完了するまで待機
            val bounds = LatLngBounds.builder()  // 位置座標の境界を作成
                                                 // LatLngは位置座標に関するクラス
            places.forEach { bounds.include(it.latLng) }
            googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 20))

            addClusteredMarkers(googleMap)
        }
    }

    private var circle: Circle? = null

    /**
     * 指定された[item]の周囲に[circle]を追加します
     */
    private fun addCircle(googleMap: GoogleMap, item: Place) {
        circle?.remove()
        circle = googleMap.addCircle {
            center(item.latLng)
            radius(1000.0)
            fillColor(ContextCompat.getColor(this@MainActivity, R.color.colorPrimaryTranslucent))
            strokeColor(ContextCompat.getColor(this@MainActivity, R.color.colorPrimary))
        }
    }

    /**
     * クラスタリングのサポートを使用してマップにマーカーを追加します。
     */
    private fun addClusteredMarkers(googleMap: GoogleMap) {
        // ClusterManager クラスを作成し、カスタム レンダラを設定します。
        val clusterManager = ClusterManager<Place>(this, googleMap)
        clusterManager.renderer = PlaceRenderer(
            this, googleMap, clusterManager
        )

        // カスタム情報ウィンドウ アダプターを設定します
        clusterManager.markerCollection.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))

        // 場所を ClusterManager に追加します。
        clusterManager.addItems(places)
        clusterManager.cluster()

        // ClusterManager を OnCameraIdleListener として設定します。
        // ズームインまたはズームアウトすると再クラスタリングが可能。
        googleMap.setOnCameraIdleListener {
            clusterManager.onCameraIdle()
        }

        // itemの周りに円を表示
        clusterManager.setOnClusterItemClickListener { item ->
            addCircle(googleMap, item)
            return@setOnClusterItemClickListener false
        }

        //
        // カメラ移動時の透明度の変更について
        //
        googleMap.setOnCameraMoveStartedListener {
            // カメラが動き始めたら、マーカーのアルファ値を半透明に変更します。
            clusterManager.markerCollection.markers.forEach { it.alpha = 0.3f }
            clusterManager.clusterMarkerCollection.markers.forEach { it.alpha = 0.3f }
        }

        googleMap.setOnCameraIdleListener {
            // カメラの動きが停止したら、アルファ値を不透明に戻します。
            clusterManager.markerCollection.markers.forEach { it.alpha = 1.0f }
            clusterManager.clusterMarkerCollection.markers.forEach { it.alpha = 1.0f }

            // カメラの動きが停止したときに再クラスタリングを実行できるように、
            // カメラの動きが停止したときにclusterManager.onCameraIdle()を呼び出します。
            clusterManager.onCameraIdle()
        }
    }
}

```

## エミュレーターでネットが使えない
自分はこちらが原因だった。

https://corycory.hateblo.jp/entry/mac/android-emulator-disconnect/

MacのDNSに`8.8.8.8`を指定した。

## マップインフォアダプター

https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.InfoWindowAdapter


## 参考
https://developers.google.com/codelabs/maps-platform/maps-platform-101-android?hl=ja

https://developers.google.com/maps/gmp-get-started?hl=ja#create-project