【Kotlin研修12日目】FusedLocationProviderClientを利用した位置情報の取得

# 位置情報の取得
[参考1: Kotlinを使用したAndroid端末の位置情報取得](https://codelabs.developers.google.com/codelabs/while-in-use-location/?hl=ja#0)
[参考2: 位置情報を取得する仕組み](https://www.atmarkit.co.jp/ait/articles/0902/12/news135.html)
**現在**の`位置情報`の`プロバイダ`は、`GPS`・`IPアドレス`・`無線APのMACアドレス`の3つ。

`FusedLocationProviderClient`を用いることで、利用する`プロバイダ`を自動で選択しながら`位置情報`を取得することができる。

## FusedLocationProviderClient
`Google`が公開する`Google Play Service`(=`API`)が提供する`Location`ライブラリに含まれる、
`位置情報`の**取得**を行うメソッドを定義するクラス。

`FusedLocationProviderClient`クラスを利用する手順は、以下の通り。

> 1. `Google Play Service`(=`API`)の追加
> 2. `app`モジュールへの`Location`ライブラリ(=`FusedLocationProviderClient`クラスの`依存関係ライブラリ`)の追加

### APIの追加
`API`の追加は、`Android Studio`の`Preference`から行うことができる。

![APIの追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/89bf0070-72c4-b985-1183-66e3c474a0f6.png)

### Locationライブラリの追加
`Location`ライブラリの追加は、`Android Studio`の`File→Project Structure`から行うことができる。

![Project Structure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/51af8af0-6d61-be43-0570-5570cb0b7fa8.png)

![Library Dependency.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/99724b61-deed-b139-b8ea-040b67518f60.png)

***

# FusedLocationProviderClientを利用した現在地の取得
[参考: 直近の位置情報を取得する](https://developer.android.com/training/location/retrieve-current?hl=ja)
`FusedLocationProviderClient`を利用して**現在の**`位置情報`を取得する手順は、以下の通り。

> 1. `FusedLocationProviderClient`・`LocationRequest`・`コールバック処理`プロパティを`lateinit`で用意し、`アクティビティ`の初期化時(=`onCreate()`)に生成・代入
> 2. `位置情報`の取得タイミング(=`LocationRequest`オブジェクト)を設定
> 3. `LocationCallback`クラスの`実装クラス`を用意し、`位置情報`の取得後に呼び出される`コールバック処理`を記述
> 4. `マニフェストファイル`(=`AndroidManifest.xml`)に`アプリケーション`が端末の`位置情報`を利用するための権限(=`パーミッション`)を付与
> 5. `アクティビティ`の**表示**直前(=`onResume()`)に、`ユーザ`から`パーミッション`が付与されていない場合は`ユーザ`に対して`パーミッションダイアログ`を表示する処理を記述
> 6. `パーミッションダイアログ`に対してユーザが操作を行った場合に呼び出される処理を記述
> 7. `ユーザ`から`パーミッション`が付与されている場合のみ`位置情報`を取得する処理を記述
> 8. `アクティビティ`**非表示**の直前(=`onPause()`)に、`位置情報`の取得処理を停止する処理を記述

## LocationRequest
`FusedLocationProviderClient`による`位置情報`の取得(=`リクエスト`)タイミングを管理するクラス。

取得タイミングを変更することによって、参照する`位置情報`の**精度**を調整できる。

## LocationCallback
`位置情報`の取得処理が**終了**した場合に呼び出される`onLocationResult()`メソッド(=`抽象メソッド`)を定義する`抽象クラス`。

## パーミッションダイアログ
`ユーザ`に対して、`パーミッションチェック`を求める`ダイアログ`。

![パーミッションダイアログ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/db7405ce-0034-7e25-6e3e-79b8014c084e.png)

***

# 各種プロパティの宣言・代入
`位置情報`の取得を行う`FusedLocationProviderClient`、
その`取得タイミング`を管理する`LocationRequest`オブジェクトは`リソース`を大量に消費するため、
`アクティビティ`を起動するタイミング(=`onCreate()`)で`オブジェクト`を生成し代入できるよう、`lateinit`キーワードを用いて宣言する。

また、[後](https://qiita.com/b150005/items/2c38f3f8925de74a0ff4#locationcallback%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E5%AE%9F%E8%A3%85)で`LocationCallback`クラスの`実装クラス`を独自に定義するため、事前に`実装クラス`のプロパティも同じく`lateinit`キーワードを用いて宣言する。

なお、`FusedLocationProviderClient`オブジェクトを生成する際は、`LocationServices`クラスの`getFusedLocationProviderClient()`メソッドを利用する。

## lateinit
[参考: lateinit変数](https://maku77.github.io/kotlin/basic/lateinit.html)
宣言時に初期値を設定(=`初期化`)せず、後から`初期化`を行う`参照型オブジェクト`に付与するキーワード。

`lateinit`キーワードを用いて宣言する場合の初期値は`null`であるため、必ず`読み書き可能`(=`ミュータブル`)な変数`var`を用いて定義する。

## LocationServices
`位置情報`を取得する`FusedLocationProviderClient`、
`ジオフェンシング`を行う`GeofencingClient`、
`Android端末`の`設定画面`を起動する`SettingsClient`のオブジェクトを生成するメソッドを定義するクラス。

### ジオフェンシング
`位置情報`を利用して特定の場所への出入りを管理する技術。

## 定義

```kotlin
// FusedLocationProviderClientオブジェクトの生成
LocationServices.getFusedLocationProviderClient(
    context: Context
): FisedLocationProviderClient
// パラメータ
// context: FusedLocationProviderClientを利用するコンテキスト

// LocationRequestオブジェクトの生成
LocationRequest.create(): LocationRequest
```

## サンプルコード

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    ...
    // FusedLocationProviderClientプロパティ
    // -> オブジェクトがリソースを大量に消費する場合、
    //    アクティビティ初期化時にオブジェクトを生成(=初期化)できるよう
    //    lateinitキーワードを用いて宣言
    private lateinit var _fusedLocationClient: FusedLocationProviderClient

    // LocationRequestプロパティ
    private lateinit var _locationRequest: LocationRequest

    // OnUpdateLocationプロパティ
    // <- 抽象クラスであるLocationCallbackの実装クラス
    private lateinit var _onUpdateLocation: OnUpdateLocation

    // アクティビティ初期化時に呼び出される処理
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // FusedLocationProviderClientプロパティの初期化
        _fusedLocationClient = LocationServices.getFusedLocationProviderClient(this@MainActivity)

        // LocationRequestプロパティの初期化
        _locationRequest = LocationRequest.create()

        // OnUpdateLocationプロパティの初期化
        _onUpdateLocation = OnUpdateLocation()
    }
}
```

# 位置情報の取得タイミングの設定
`位置情報`の取得タイミングを管理する`LocationRequest`のプロパティに、各種`設定値`を代入する。

なお、`位置情報`の**取得精度**を表す`priority`プロパティに代入する値は、`LocationRequest`のクラス定数を利用する。

## LocationRequestクラスのプロパティ
[参考: LocationRequest](https://developer.huawei.com/consumer/en/doc/locationrequest)

|プロパティ名|内容|既定値|
|:--|:--|:--|
|`interval`|基本となる取得間隔|`3600000`[ms]|
|`fastestInterval`|最短の取得間隔|`600000`[ms]|
|`priority`|位置情報の取得精度<br>※`LocationRequest`クラス定数を利用|`PRIORITY_HIGH_ACCURACY`|

### 位置情報の取得精度を表すLocationRequestクラス定数
`LocationRequest`クラスの`priority`プロパティは、`位置情報`の`取得精度`に関する設定値を指す。

なお、`PRIORITY_NO_POWER`に関しては、電力消費を最小限に抑えるため、
別の`アプリケーション`によって取得されたタイミングでその`位置情報`を利用する。

|定数名|内容|値|
|:--|:--|:--|
|`PRIORITY_HIGH_ACCURACY`|高精度<br>`番地`レベル|`100`(既定)|
|`PRIORITY_BALANCED_POWER_ACCURACY`|中精度<br>`丁目`レベル|`102`|
|`PRIORITY_LOW_POWER`|低精度<br>`市・区`レベル|`104`|
|`PRIORITY_NO_POWER`|他アプリに依存|`105`|

## サンプルコード

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    ...
    // LocationRequestプロパティ
    private lateinit var _locationRequest: LocationRequest

    // アクティビティ初期化時に呼び出される処理
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // LocationRequestプロパティの初期化
        _locationRequest = LocationRequest.create()

        // LocationRequestプロパティがnullでないことを保証
        // <- lateinitで宣言したオブジェクトのプロパティを変更する場合は
        //    オブジェクトがnullでないことの保証が推奨
        _locationRequest?.let {

            // 基本取得間隔[ms]
            it.interval = 5000

            // 最短取得間隔[ms]
            it.fastestInterval = 1000

            // 位置情報の取得精度
            it.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        }
        ...
    }
}
```

# LocationCallbackクラスの実装
`位置情報`の取得処理が**終了**した場合の`コールバックメソッド`にあたる`onLocationResult()`メソッドは、
`LocationCallback`クラスで定義される、`位置情報`が利用可能になった際に呼び出される`抽象メソッド`であるため、
`LocationCallback`クラスの実装クラスを用意し、`オーバーライド`して`位置情報`の**取得処理が終了した場合に実行する処理**を記述する。

また、`LocationCallback.onLocationResult()`メソッドは引数に`LocationResult`オブジェクトをとるが、
`位置情報`が取得できなかった場合はその値が`null`となるため、
`コールバック処理`を記述する際に、`セーフコール演算子`(=`?`)と`let`関数を用いて`NonNull`であることを保証する必要がある。

さらに、`LocationResult`オブジェクトの、取得した`直近の位置情報`を格納する`lastLocation`プロパティ(=`Location`オブジェクト)は、
`端末の再起動直後`など、場合によっては値が`null`であることがあるため、同様に継続処理を行う場合は`NonNull`であることを保証する必要がある。

## LocationResult
`位置情報`の`取得処理結果`を、過去の取得結果も含めて`リスト`で管理するクラス。

## Location
`経緯度`・`タイムスタンプ`・`方位`・`高度`・`速度`などの`地理的情報`を保持するクラス。

## 定義

```kotlin
// デバイスの位置情報が利用可能である場合に呼び出される抽象メソッド
LocationCallback.onLocationResult(result: LocationResult?): Unit
// パラメータ
// result: 利用可能な最新の位置情報の取得結果を保持するLocationResultオブジェクト
```

## サンプルコード

```kotlin:MainActivity.kt
// 位置情報の取得処理終了時に呼び出される処理の実装クラス
private inner class OnUpdateLocation: LocationCallback() {

    // 位置情報の取得処理終了時に呼び出される処理
    override fun onLocationResult(result: LocationResult?) {

        // 取得結果がnullでないことを保証
        result?.let {

            // 直近の位置情報を保持するlastLocationプロパティ
            val location = it.lastLocation

            // 直近の位置情報がnullでないことを保証
            location?.let {

                // 直近の経緯度
                _latitude = it.latitude
                _longitude = it.longitude
                ...
            }
        }
    }
}
```

# マニフェストファイルへのパーミッションの記述
`アプリケーション`が端末の`位置情報`を利用できるよう、`マニフェストファイル`(=`AndroidManifest.xml`)に`<uses-permission>`タグを追記する。

なお、`マニフェストファイル`に記述したタグは、あくまで「`ユーザ`による利用権限(=`パーミッション`)の付与が必要な項目」であるため、
[別途](https://qiita.com/b150005/items/2c38f3f8925de74a0ff4#%E3%83%91%E3%83%BC%E3%83%9F%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%80%E3%82%A4%E3%82%A2%E3%83%AD%E3%82%B0%E3%81%AE%E8%A1%A8%E7%A4%BA)ユーザに対して`パーミッションダイアログ`を表示することで、`ユーザ`から利用許可を得られるようにする必要がある。

## サンプルコード

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>

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

# パーミッションダイアログの表示
`アプリケーション`が`端末情報`を利用する場合、`ユーザ`から利用権限(=`パーミッション`)を付与してもらうために、
`アクティビティ`の表示直前(=`onResume()`)に`パーミッションダイアログ`を表示する必要がある。

ただし、事前(以前)にユーザから既に`パーミッション`が付与されている場合は`パーミッションダイアログ`を表示する必要はないため、
`パーミッションダイアログ`を表示する前に`セルフパーミッションチェック`を行う必要がある。

なお、ユーザから`アプリケーション`に対して設定された`パーミッション`情報を取得するメソッドは`ContextCompat`クラスで定義され、
`パーミッションダイアログ`を表示するメソッドは`ActivityCompat`クラスで定義される。

ここで、`ContextCompat`クラスは`アクティビティクラス`が間接的に継承する`ActivityCompat`クラスが継承しているため、
`ActivityCompat`クラスのメソッドとして利用することができる。

また、`アプリケーション`に対して設定された`パーミッション`の値は`PackageManager`のクラス定数を利用する。

## PackageManager
各`アプリケーション`に固有に設定された情報を取得するメソッドや、`パーミッション`情報をクラス定数として用意する`抽象クラス`。

## 定義

```kotlin
// アクティビティによるセルフパーミッションチェック
// <- ActivityCompatはContextCompatクラスを継承しているため、
//    ActivityCompat.checkSelfPermission()としても呼び出せる
ContextCompat.checkSelfPermission(
    @NonNull context: Context,
    @NonNull permission: String
): Int
// パラメータ
// context: パーミッションチェックを行うアクティビティクラス(=コンテキスト)
// permission: パーミッションチェックを行うパーミッション名
// -> "Manifest.permission.<パーミッションの種類>"で記述

// パーミッションダイアログの表示
ActicityCompat.requestPermissions(
    @NonNull activity: Activity, 
    @NonNull permissions: Array<String!>, 
    @IntRange(0) requestCode: Int
): Unit
// パラメータ
// activity: パーミッションダイアログを表示するアクティビティ
// permissions: ユーザに許可を求めるパーミッションリスト
// requestCode: 下記
// ActivityCompat.OnRequestPermissionsResultCallbackインタフェースの
// onRequestPermissionsResult()メソッド(後述)と一対一で対応させる、
// 0以上のInt型で表す固有のリクエストコード
```

### パーミッション情報を表すPackageManagerクラス定数

|定数名|内容|
|:--|:--|
|`PERMISSION_GRANTED`|`許可`|
|`PERMISSION_DENIED`|`不許可`|

## サンプルコード

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    // アクティビティの表示直前に呼び出される処理
    override fun onResume() {
        super.onResume()

        // アクティビティによるセルフパーミッションチェックの結果、
        // 位置情報の利用が許可されていない場合の処理
        if (ActivityCompat.checkSelfPermission(
                this@MainActivity,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED
        ) {

            // パーミッションチェックを行うパーミッションリスト
            val permissions = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)

            // ユーザに許可を求めるパーミッションダイアログの表示
            ActivityCompat.requestPermissions(
                this@MainActivity,
                permissions,
                1000
            )

            // onResume()メソッドの終了
            return
        }
        ...  // アクティビティによるセルフパーミッションチェックの結果、
             // 位置情報の利用が許可されている場合の処理
    }
}
```

# パーミッションダイアログが操作された場合の処理
`パーミッションダイアログ`が操作された場合に自動的に呼び出される処理(=`コールバック処理`)は、`ActivityCompat`クラス内で定義される`OnRequestPermissionsResultCallback`インタフェースの`onRequestPermissionsResult()`メソッドを`オーバーライド`して定義する。

`onRequestPermissionsResult()`メソッドは`パーミッションダイアログ`を表示する処理と`リクエストコード`で紐づくため、`パーミッション`情報をそのまま受け取ることができるが、
`Android Studio`の仕様上、`アクティビティ`による`セルフパーミッションチェック`も必要とする。

## 定義

```kotlin
// パーミッションダイアログが操作された場合に呼び出されるコールバックメソッド
ActivityCompat.OnRequestPermissionsResultCallback.onRequestPermissionsResult(
    requestCode: Int, 
    @NonNull permissions: Array<String!>, 
    @NonNull grantResults: IntArray
): Unit
// パラメータ
// requestCode: ActivityCompat.requestPermissions()メソッドと一対一で対応させる、
//              (0以上の)Int型で表す固有のリクエストコード
// permissions: ユーザに許可を求めたパーミッションリスト
// grantResults: PackageManagerのクラス定数で表される、
//               ユーザが設定したパーミッションの値を格納するリスト
// <- パーミッションリスト内の順番(=インデックス番号)とそれぞれ一対一で対応
```

## サンプルコード

```kotlin:MainActivity.kt
// パーミッションダイアログが操作された場合に呼び出されるコールバックメソッド
override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {

    // ユーザ操作によって利用許可が下りていた場合
    if (requestCode == 1000 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

        // アプリケーション側でセルフパーミッションチェックを行った結果、拒否されていた場合
        if (ActivityCompat.checkSelfPermission(
                this@MainActivity,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            // 何も実行せずonRequestPermissionsResult()メソッドを終了
            return
        }
        ...  // アクティビティによるセルフパーミッションチェックの結果、
             // 位置情報の利用が許可されている場合の処理
    }
}
```

# 位置情報の取得
[参考: 研修8日目(Looper)](https://qiita.com/b150005/items/6cd72acc75920be893f6#looper)
`FusedLocationProviderClient`クラスの`requestLocationUpdates()`メソッドを用いて`位置情報`を取得する。

ただし、`位置情報`の取得処理を行う場合は、**事前に**`アクティビティ`による`セルフパーミッションチェック`を行う必要がある。

`セルフパーミッションチェック`の結果、`ユーザ`から`パーミッション`を付与されていない場合は、
「**何も実行しない**」または「`パーミッションダイアログ`**の表示**」などの処理を行う必要がある。

なお、`requestLocationUpdates()`メソッドは、
`位置情報`の`取得間隔`を表す`LocationRequest`オブジェクト、
`位置情報`に**変更(=更新)があった場合に呼び出される処理**を実装した`LocationCallback`の`実装クラス`オブジェクト、
その処理を動作させる`スレッド`を指定するための`Looper`オブジェクトを引数にとる。

## 定義

```kotlin
FusedLocationProviderClient.requestLocationUpdates(
    request: LocationRequest, 
    callback: LocationCallback, 
    looper: Looper
): Unit
// パラメータ
// request: 位置情報更新に関する設定情報を保持するLocationRequestオブジェクト
// callback: 位置情報に変更(=更新)があった場合に呼び出される
//           LocationCallbackの実装クラスオブジェクト
// looper: 処理の動作スレッドを指定するLooperオブジェクト
```

## サンプルコード

```kotlin:MainActivity.kt
// FusedLocationProviderClientプロパティの初期化
_fusedLocationClient = LocationServices.getFusedLocationProviderClient(this@MainActivity)

// LocationRequestプロパティの初期化
_locationRequest = LocationRequest.create()

// LocationCallbackプロパティの初期化
_onUpdateLocation = OnUpdateLocation()

// アクティビティによるセルフパーミッションチェックの結果、
// 位置情報の利用が許可されていない場合の処理
// -> パーミッションダイアログを表示
if (ActivityCompat.checkSelfPermission(
    this@MainActivity,
    Manifest.permission.ACCESS_FINE_LOCATION
    ) != PackageManager.PERMISSION_GRANTED
) {

    // パーミッションチェックを行うパーミッションリスト
    val permissions = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)

    // パーミッションダイアログの表示
    ActivityCompat.requestPermissions(
        this@MainActivity,
        permissions,
        1000
    )

    // 何も実行せずスコープを抜ける
    return
}

// アクティビティによるセルフパーミッションチェックの結果、
// 位置情報の利用が許可されている場合の処理
// -> 位置情報を取得
_fusedLocationClient.requestLocationUpdates(
    _locationRequest,
    _onUpdateLocation,
    mainLooper
)
```

# 位置情報の取得処理の停止
`アクティビティ`の終了時(=`onPause()`)など、`位置情報`を取得する必要がなくなる場合は、
`FusedLocationProviderClient`クラスの`removeLocationUpdates()`メソッドを用いて
`位置情報`の`取得処理`を**停止**させる必要がある。

## 定義

```kotlin
FusedLocationProviderClient.removeLocationUpdates(
    callback: LocationCallback
): Unit
// パラメータ
// callback: 位置情報に変更(=更新)があった場合に呼び出される
//           LocationCallbackの実装クラスオブジェクト
```

## サンプルコード

```kotlin:MainActivity.kt
// FusedLocationProviderClientプロパティの初期化
_fusedLocationClient = LocationServices.getFusedLocationProviderClient(this@MainActivity)

// LocationCallbackプロパティの初期化
_onUpdateLocation = OnUpdateLocation()

// アクティビティの非表示直前に呼び出される処理
override fun onPause() {
    super.onPause()

    // 位置情報の取得を停止
    _fusedLocationClient.removeLocationUpdates(_onUpdateLocation)
}
```