Android Location APIで位置情報(緯度と経度)を取得する ※※LocationManager非推奨※※

# 前書き
Androidアプリで位置情報を取得しようと調査していたら現在は非推奨になっているサンプルが多数ありました。LocationManagerなどは[非推奨](https://developer.android.com/reference/android/location/package-summary)になっています。deprecatedではないようですね。推奨されている方法を調査してまとめてみることにしました。

# 構成
1. Kotlinで書きます。  
非推奨のサンプルは時が経っていることを表し故にJavaで書かれています。Kotlinで書きます。
1. 現在地を取得します。  
既に公開されているサンプルコードに敬意を払いつつもAndroid 6以降の[実行時のパーミッション リクエスト](https://developer.android.com/training/permissions/requesting?hl=ja)と同時にコードが記載されているので複雑に感じました。まず目的である現在地を取得するサンプルを書きます。
目的の処理に先に辿り着くことで挫折を防ぎます:v:
1. 実行時のパーミッション リクエストは考慮しません。  
パーミッションは結局必要なものですが一時的に現実逃避します:runner::runner::runner:  
一度にたくさんの状況を把握することが苦手な筆者が理解して記事を投稿出来る(説明出来る)ようにしますのでパーミッションの処理は書きません。

# 動作環境
|　||
|---|---|
|macOS|High Sierra 10.13.6|
|Android Studio|3.2|
|AVD|Pixel 2 Android 9 (API 28)|

# サンプルプロジェクト
[GitHub](https://github.com/nokiyo8774/LocationSample)に公開しています。併せてご覧ください。

# Kotlinで書く
プロジェクトを作成するだけですが、スクリーンショットを載せておきます。
Kotlinにチェックします。
<img width="1012" alt="スクリーンショット 2018-09-29 21.47.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/240974/b34446ed-d40e-26e7-e4c3-25d8ef6d0387.png">

とりあえずKitKatにします。
<img width="1012" alt="スクリーンショット 2018-09-29 21.48.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/240974/0aa0fc9d-b581-73e3-5ec0-d01f7075ef20.png">

Google Mapを使いたいので「Google Maps Activity」で。
<img width="1012" alt="スクリーンショット 2018-09-29 21.48.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/240974/f0009652-8ca3-33ba-0114-30e05a606a1b.png">


# まず現在地を取得する
1. パーミッションの確認と追加します。  
AndroidManifest.xmlにパーミッションが必要になります。前述の`実行時のパーミッション リクエスト`とは異なりますのですぐに終わります。プロジェクト作成時に「Google Maps Activity」 を選択すると付与されていると思いますが確認して無ければ追加します。

    ```xml:AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
```

1. Google Play Servicesを導入します。
今回は`FusedLocationProviderClient`や`LocationRequest`などを利用します。これら[Google Play Services](https://developers.google.com/android/guides/setup)の機能を利用するためにbuild.gradle以下を記述します。

    ```:build.gradle
implementation 'com.google.android.gms:play-services-location:15.0.1'
```
build.gradleをご覧頂きましたが、筆者の環境では以下にエラー線がありますが無視して動作させます。なお、この記事の終わりの方で「補足」として解消する方法についても解説します。現在地の取得を優先します:grinning:

    ```:build.gradle
    implementation 'com.android.support:appcompat-v7:28.0.0'
```
1. 緯度と経度を取得します。
[公式](https://developer.android.com/training/location/receive-location-updates)を参考にしながらプログラムを書きます。「Google Maps Activity」が意味無くなりましたが、シンプルにしたいのでMapActivity.ktを以下のように書き換えました。

    ```kotlin:MapActivity.kt
package com.example.devnokiyo.locationsample

    import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.Looper
import android.widget.Toast
import com.google.android.gms.location.*

    class MapsActivity : AppCompatActivity() {
        // 位置情報を取得できるクラス
        private lateinit var fusedLocationClient : FusedLocationProviderClient

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            fusedLocationClient = FusedLocationProviderClient(this)

            // どのような取得方法を要求
            val locationRequest = LocationRequest().apply {
                // 精度重視(電力大)と省電力重視(精度低)を両立するため2種類の更新間隔を指定
                // 今回は公式のサンプル通りにする。
                interval = 10000                                   // 最遅の更新間隔(但し正確ではない。)
                fastestInterval = 5000                             // 最短の更新間隔
                priority = LocationRequest.PRIORITY_HIGH_ACCURACY  // 精度重視
            }

            // コールバック
            val locationCallback = object : LocationCallback() {
                override fun onLocationResult(locationResult: LocationResult?) {
                    // 更新直後の位置が格納されているはず
                    val location = locationResult?.lastLocation ?: return
                    Toast.makeText(this@MapsActivity,
                        "緯度:${location.latitude}, 経度:${location.longitude}", Toast.LENGTH_LONG).show()
                }
            }

            // 位置情報を更新
            fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper())
        }
}
```
この状態では`fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper())`にエラー線が表示されていますが無視してエミュレーターで動かして見ます。動いているように見えていますが実は動いていません。
![スクリーンショット 2018-09-30 0.51.48.png](https://qiita-image-store.s3.amazonaws.com/0/240974/de9e3c06-e1f9-5282-e958-90b2ea3e98b4.png)
AVDはAndroid 9でありAndroid 6以降の実行時のパーミッション リクエストを実装しておらずパーミッションを許可出来ないからです。実装が完了しない間は手動で許可します。
Settings > Security and location > location > App-level permissions
　LocationSampleに許可を与えます。
![スクリーンショット 2018-09-30 0.57.29.png](https://qiita-image-store.s3.amazonaws.com/0/240974/4822d4e6-c829-d30f-dbd7-f695067b3b99.png)
もう一度、エミュレーターで動かしますと如何でしょうか。Toastで緯度と経度が表示されています。
<img width="261" alt="スクリーンショット 2018-09-30 1.06.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/240974/3b5bc571-8ad6-faec-0d52-b95b37dfceee.png">
ここまでで位置情報を取得することができました:thumbsup:
AVDで動作させているときに手動でパーミッションを許可しているにも関わらず、サンプルコードを緯度と経度が表示されないときはLocationを手動で入力してみてください。![スクリーンショット 2018-10-02 0.20.40.png](https://qiita-image-store.s3.amazonaws.com/0/240974/ec8f07e1-79b7-51d7-b264-ebacff66c0cd.png)
1. 定期取得をやめてみます
サンプルコードでは10秒毎に緯度と経度が表示されていると思います。カーナビアプリなどリアルタイムに位置情報を取得したい場合、定期的に位置情報を取得します。しかし、筆者の作成したいアプリは起動時だけ位置情報を取得出来れば良いので、一度位置情報を取得したら定期取得をやめます。


```kotlin:MapsActivity.kt
        // コールバック
        val locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult?) {
                    :
                    :
                // 初回起動でしか位置情報を取得しないサンプルなので位置情報の定期取得を止める。
                fusedLocationClient.removeLocationUpdates(this)
            }
        }

```
この状態でエミュレーターを動かすと一度だけ緯度と経度が表示されるようになりました。

# 補足
標準で作成されたbuild.gradleのエラーはバージョンの競合を意味しています。

```:build.gradle
implementation 'com.android.support:appcompat-v7:28.0.0'
```
以下を追加してあげれば解消します。Android 9(API 28)未満のAPIを利用すればこの問題は発生しないかもしれません。

```:build.gradle
    implementation 'com.android.support:support-v4:28.0.0'
```

# 終わりに
筆者自身初めて位置情報を取得するアプリを作成することになり調査したので、今後作込むときにどのような挙動を示すか未知数ですが、ひとまずはLocationManagerを利用しない方法で位置情報が取得できました。実機でも動作していました。旧世代向けの環境ではまだテストしていません。筆者環境ではGoogle Maps Activityを利用して地図上に現在地を表示したのですが、記事が長くなりそうでしたので位置情報の取得に留めさせて頂きました。記事を書きながら断念したパターンです:sweat: