【Kotlin】FusedLocationClientを使った位置情報の求め方

kotlin

AndroidManifestにPermissionを追加する

位置情報にアクセスするにはAndroidManifestにPermissionを追加する必要があります。

<manifest 省略>

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

    <application
         省略
    </application>

</manifest>

上記のようにmanifestタブ内でapplicationタブの外側に二種類のpermissionを追加してください。
ACCESS_FINE_LOCATIONはGPSを使用した高精度な位置情報、ACCESS_COARSE_LOCATIONは周りのWi-Fiなどを利用した大まかな位置情報を求めることを許可します。

権限をリクエストするポップアップを表示する

上記のような位置情報にアクセスする権限を要求するポップアップを表示させます。
手順としてはアプリの起動時(任意のタイミング)にアプリの位置情報の権限が許可されているか確認し、拒否されていれば表示するという流れです。

class LocationManager(private val context: Context){

private lateinit var requestPermissionLauncher: ActivityResultLauncher<Array<String>>

fun register(activity: ComponentActivity, onGranted: (() -> Unit)? = null, onDenied: (() -> Unit)? = null) {
requestPermissionLauncher = activity.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
when {
permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> {
onGranted?.invoke()
}
else -> {
onDenied?.invoke()
}
}
}
}

fun requestLocationPermission(onGranted: (() -> Unit)? = null) {
when {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED -> {
onGranted?.invoke()
}
else -> {
Log.d("Permission", "is denied")
requestPermissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION))
}
}
}
}

具体的には上記のようなクラスを作ります。
register関数は権限要求の結果を処理するための関数です。
Activity内でonGrantedとonDenied(許可された時と拒否された時)の具体的な処理を実装できるようにしています。
requestLocationPermission関数は実行時に位置情報の権限の状態を確認し、拒否されている場合はポップアップを表示し、予め許可されていたら、register関数と同様にActivity内で任意の処理を実行できるようにしています。

ActivityのOnCreate内で

locationManager = LocationManager(applicationContext)
locationManager.register(
this,
onGranted = {
Log.d("requestPermissionLauncher", "is granted")
    ※許可された時の処理を入力してください
    ※例:位置情報の取得
},
onDenied = {
Log.d("requestPermissionLauncher", "is denied")
    ※拒否された時の処理を入力してください
}
)
locationManager.requestLocationPermission(
onGranted = {
Log.d("requestPermissionLauncher", "is granted")
    ※許可されていた時の処理を入力してください
    ※例:位置情報の取得
}
)

上記の順番で実行すれば正常に動作します。
これでアプリが位置情報の権限をリクエストできるようになりました。

FusedLocationClientを使って端末の位置情報を取得する

位置情報の権限をリクエストできたら、次は実際に位置情報を取得する段階に入ります。
今回はFusedLocationClientを使って端末の位置情報を取得します。
まずはアプリケーションレベルのGradleにplay-services-locationを追加します。

implementation ("com.google.android.gms:play-services-location:21.3.0")

位置情報を求める関数とその関数のコールバックを作成します。
私の場合はMainActivity内に作成しました。

private fun requestLocation(listener: LocationCallbackListener) {
listener.onLocationRequest()
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED
) {
Log.d("
requestLocation", "PERMISSION -> denied")
} else {
Log.d("
requestLocation", "PERMISSION -> granted")
fusedLocationClient.lastLocation
.addOnSuccessListener { location ->
listener.onLocationResult(location)
}
.addOnFailureListener { exception ->
listener.onLocationError(exception)
}
}
}

interface LocationCallbackListener {
fun onLocationRequest()
fun onLocationResult(location: Location?)
fun onLocationError(exception: Exception)
}

private val callbackListener = object : LocationCallbackListener {
override fun onLocationRequest() {
Log.d("LocationRequest", "started")
    ※リクエスト開始時の処理を入力してください
}

override fun onLocationResult(location: Location?) {
Log.d("LocationResult", "latitude:${location?.latitude}")
Log.d("LocationResult", "longitude:${location?.longitude}")
    ※位置情報取得後の処理を入力してください
}

override fun onLocationError(exception: Exception) {
Log.e("LocationRequest", "Exception: ${exception.localizedMessage}")
    ※エラー発生時の処理を入力してください
}
}

requestLocation関数では、位置情報の権限が許可されている場合にfusedLocationClientを使って、現在地を取得しています。
LocationCallbackListenerというインターフェイスを作成し、その実装はcallbackListenerにしています。
callbackListenerを見てみると、onLocationRequest、onLocationResult、onLocationErrorと三つの段階に分かれています。
リクエスト時、結果が出たとき、エラーが発生したときにそれぞれの関数を呼び出すようにしています。
requestLocation関数が呼び出された時点でonLocationRequestも呼び出されます。
fusedLocationClientが現在位置の取得に成功すると、onLocationResultが実行されて、現在地をLog上に流してくれます。
逆に失敗したときはエラー内容を流すようにしています。
実際にアプリを作る際はここら辺に業務上に必要な処理を記述することになると思います。
requestLocation関数はlocationManagerのonGranted内に記述して使ってください。

まとめ

今回使ったソースコードの全文を載せておきます。

class MainActivity : ComponentActivity() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var locationManager: LocationManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

locationManager = LocationManager(applicationContext)
locationManager.register(
this,
onGranted = {
Log.d("requestPermissionLauncher", "is granted")
requestLocation(callbackListener)
},
onDenied = {
Log.d("requestPermissionLauncher", "is denied")
}
)
locationManager.requestLocationPermission(
onGranted = {
Log.d("Permission", "is granted")
requestLocation(callbackListener)
}
)

setContent {
       省略
}
}

private fun
requestLocation(listener: LocationCallbackListener) {
listener.onLocationRequest()
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED
) {
Log.d("
requestLocation", "PERMISSION -> denied")
} else {
Log.d("
requestLocation", "PERMISSION -> granted")
fusedLocationClient.lastLocation
.addOnSuccessListener { location ->
listener.onLocationResult(location)
}
.addOnFailureListener { exception ->
listener.onLocationError(exception)
}
}
}

interface LocationCallbackListener {
fun onLocationRequest()
fun onLocationResult(location: Location?)
fun onLocationError(exception: Exception)
}

private val callbackListener = object : LocationCallbackListener {
override fun onLocationRequest() {
Log.d("callbackListener", "onLocationRequest")
}

override fun onLocationResult(location: Location?) {
Log.d("callbackListener", "success -> latitude:${location?.latitude}")
Log.d("callbackListener", "success -> longitude:${location?.longitude}")
}

override fun onLocationError(exception: Exception) {
Log.e("callbackListener", "Exception: ${exception.localizedMessage}")
}
}
}

class LocationManager(private val context: Context){

private lateinit var requestPermissionLauncher: ActivityResultLauncher<Array<String>>

fun register(activity: ComponentActivity, onGranted: (() -> Unit)? = null, onDenied: (() -> Unit)? = null) {
requestPermissionLauncher = activity.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
when {
permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> {
onGranted?.invoke()
}
else -> {
onDenied?.invoke()
}
}
}
}

fun requestLocationPermission(onGranted: (() -> Unit)? = null) {
when {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED -> {
onGranted?.invoke()
}
else -> {
Log.d("Permission", "is denied")
requestPermissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION))
}
}
}
}