FusedLocationProviderClientでlastLocationが取得できない場合がある

# FusedLocationProviderClient
Android SDK で、継続的にではなく一度だけ現在地を知りたいとき、[FusedLocationProviderClient#getLastLocation](https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient.html#getLastLocation()) を使う場合が多いと思います。[公式のドキュメント](https://developer.android.com/training/location/retrieve-current)にも以下のように書いてあります。

> Specifically, use the fused location provider to retrieve the device's last known location.

この FusedLocationProviderClient#getLastLocation を使っていて、何故か現在地が取得できないという事象に遭遇しました。これを解決するうえで LastLocation に対する理解が深まったので、せっかくだし残しておこうと思います。

<details><summary>【補足】再現性について</summary><div>
ちなみに私が知る限り、某メーカーのやや古い端末でだけ（2016年モデルで、レビューを見る限り評判もよくない）でしか再現しません。それより3年も古い Nexus5 の Android を同じバージョンにしても再現しないので、かなりニッチな事象だと思います。
とはいえ、無視できないのが Android 開発の宿命というかなんというか…</div></details>

# 問題となった事象
getLastLocation に対する **OnSuccessListener に制御が渡ってきているのに、得られた Location は null** というのがそれです。そのとき Activity に書いたコードが以下です。
ちなみに適切なパーミッションが付与されていないと動作しませんが、そのあたりについては割愛します。

```kotlin
LocationServices.getFusedLocationProviderClient(this)
    .lastLocation
    .addOnSuccessListener {
        // (1)Locationを使った処理
    }
    .addOnFailureListener {
        // (2)エラー発生時の処理
    }
```

(2)のほうに制御が渡ってないのに(1)で得られる戻り値は null だというのが問題になっていました。

# 原因
[Stack Overflow の記事](https://stackoverflow.com/questions/29441384/fusedlocationapi-getlastlocation-always-null)がとても参考になりました。
FusedLocationProviderClient#getLastLocation で得られるのは、**端末(OS)が取得した最後の位置情報**です。getLastLocation を実行したときに位置情報が取得される訳ではありません。自分以外のアプリかOS自身が位置情報を取得したことがあれば、端末に残っているその情報を使います。なので **getLastLocation で得られた現在地は、今まさに自分がいる位置と完全に同一という訳ではありません**。
とはいえ公式によると、大抵の場合はそれで要件を満たすことができるとのことです。

> In most cases, you are interested in the user's current location, which is usually equivalent to the last known location of the device.

では、まだ位置情報を取得したことがない場合はどうなるのでしょうか。
端末によって異なるのだと思いますが、今回問題となったのがそのケースで、「端末に残っている位置情報へのアクセス処理には成功したが、端末で一度も位置情報を取ったことがないので null が返ってくる」ということになります。**制御は OnSuccessListener に渡されるけれども戻り値が null** になるのです。
こんなとき、例えば Google Map アプリを起動して現在地を確認してから再度自分のアプリを起動すると、このエラーは発生しません。Google Map アプリで現在地を取得したことがあるからです。
件の端末では再起動をするたびに位置情報がクリアされてしまっていたので、再起動のたびにこの事象が再現していました（くどいようですが、他の端末では同じことをしても再現しませんでした）。

# 対策
getLastLocation でなく requestLocationUpdates だと確実に位置情報を取得できます。
常に requestLocationUpdates するのもひとつの手ですが、私は getLastLocation のフォールバックとして使うことを考えました。

```kotlin
val locationClient = LocationServices.getFusedLocationProviderClient(this)

locationClient
    .lastLocation
    .addOnSuccessListener {
        if (it != null) {
            // (a)LastLocation が得られたときの処理
        } else {
            val request = LocationRequest.create()
                .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
                .setInterval(500)
                .setFastestInterval(300)

            locationClient
                .requestLocationUpdates(
                    request,
                    object : LocationCallback() {
                        override fun onLocationResult(result: LocationResult) {
                            // (b)LocationUpdates で得た時の処理

                            // 現在地だけ欲しいので、1回取得したらすぐに外す
                            locationClient.removeLocationUpdates(this)
                        }
                    },
                    null
                )
        }
    }
```

# まとめ
FusedLocationProviderClient#getLastLocation で現在地が取得できないときの対策について考えました。
再現する端末も限られるので、過度に意識する必要がないかもしれませんが、同じような事象が発生したときに誰かの参考になれば幸いです。