🌏

Androidで位置情報を取得する

2024/08/21に公開

はじめに

今回はAndroidで位置情報を取得できるAndroidのLocation APIを使用します。
LocationManager等もありますが現在非推奨らしいのでLocation APIを使用していきます。

位置情報を取得する

位置情報を取得するためにパーミッションが必要なのであらかじめ記述しておきましょう
AndroidManifest.xmlに以下の権限をを追加しておきます。

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

ACCESS_FINE_LOCATIONが正確な位置情報
ACCESS_COARSE_LOCATIONがおおよその位置情報の取得に必要な権限です。
権限の許可を求める部分は位置情報を取得するクラスの方で書くので今は大丈夫です。
また、依存関係を追加する必要があるので
libs.versions.toml

libs.versions.toml
[versions]
//他のバージョン(元から書いてあるやつ)
playServicesLocation = "21.3.0"
[libraries]
//他のライブラリ(元から書いてあるやつ)
play-services-location = { group = "com.google.android.gms", name = "play-services-location", version.ref = "playServicesLocation" }

build.gradle.kts(Module:app)

build.gradle.kts
implementation(libs.play.services.location)

を追記しておきましょう
gradleを変更したらSync Nowを忘れずに!!

次にMainActivityを用意します

MainActivity.kt
import GPSLocationManager
import android.location.Location
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.nenfuat.getgps.ui.theme.GetGPSTheme

class MainActivity : ComponentActivity() {
    private lateinit var gpsLocationManager: GPSLocationManager
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        gpsLocationManager = GPSLocationManager(this)


        enableEdgeToEdge()
        setContent {
            GetGPSTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    GPSViewer(
                        modifier = Modifier.padding(innerPadding),
                        gpsLocationManager
                    )
                }
            }
        }
    }
}

@Composable
fun GPSViewer(modifier: Modifier = Modifier,gpsLocationManager: GPSLocationManager) {
    var latitude by remember { mutableStateOf("") }
    var longitude by remember { mutableStateOf("") }
    Column (modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ){
        Text(
            text = "緯度:$latitude\n経度:$longitude",
            modifier = modifier
        )
        Button(onClick = {
            gpsLocationManager.getLastLocation(object : GPSLocationManager.MyLocationCallback {
                override fun onLocationResult(location: Location?) {
                    if (location != null) {
                        latitude = location.latitude.toString()
                        longitude = location.longitude.toString()
                    }
                }

                override fun onLocationError(error: String) {
                    // エラー処理
                }
            })
        }) {
            Text(
                text = "位置情報取得",
            )
        }
    }
}

これで準備ができたので実際に位置情報を取得していきましょう。

端末が最後に取得した位置情報を取得する

位置情報を取得するクラスを作っていきます。

GPSLocationManager.kt
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import androidx.core.app.ActivityCompat
import com.google.android.gms.location.*
import com.nenfuat.getgps.MainActivity

class GPSLocationManager(private val context: Context) {

    private var fusedLocationClient: FusedLocationProviderClient =
        LocationServices.getFusedLocationProviderClient(context)

    interface MyLocationCallback {
        fun onLocationResult(location: Location?)
        fun onLocationError(error: String)
    }

    fun getLastLocation(callback: MyLocationCallback) {
        // 位置情報を取得するためのPermissionチェック
        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
            ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // 必要な場合、パーミッションを要求
            if (context is MainActivity) {
                ActivityCompat.requestPermissions(context, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION), 1000)
            }
            return
        }
        requestLocation(callback)
    }

    @SuppressLint("MissingPermission")
    private fun requestLocation(callback: MyLocationCallback) {
        fusedLocationClient.lastLocation
            .addOnSuccessListener { location: Location? ->
                if (location != null) {
                    callback.onLocationResult(location)
                } else {
                    callback.onLocationError("Location is null")
                }
            }
            .addOnFailureListener { exception ->
                callback.onLocationError(exception.message ?: "Unknown error")
            }
    }
}



解説

getLastLocation

fusedLocationClient.lastLocationを利用して
端末が最後に取得した位置情報を返す関数です。
位置情報の更新は行わないため端末の位置情報が変化しないと何回実行しても同じ値が返されます。


これを実行すると

このような画面になります
位置情報取得ボタンを押すと

権限を求められるので許可してください
許可後もう一度押すと位置情報を取得できます

しかしこれだと端末が最後に取得した位置情報を一度しか取得できないため、
改良して定期的に位置情報を取得するようにしましょう。

定期的に位置情報を取得する

GPSLocationManager.kt
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import com.google.android.gms.location.*
import androidx.core.app.ActivityCompat
import com.nenfuat.getgps.MainActivity

class GPSLocationManager(private val context: Context) {

    private var getRate: Long = 10000//取得頻度(ms)
    private var minRate: Long = 5000//更新頻度(ms)

    private var fusedLocationClient: FusedLocationProviderClient =
        LocationServices.getFusedLocationProviderClient(context)
    private var locationRequest: LocationRequest? = null
    private var locationCallback: LocationCallback? = null

    interface MyLocationCallback {
        fun onLocationResult(location: Location?)
        fun onLocationError(error: String)
    }

    init {
        // LocationRequestの設定
        locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, getRate) // 10秒ごとに取得
            .setMinUpdateIntervalMillis(minRate) // 最小間隔5秒
            .build()
    }

    fun startLocationUpdates(callback: MyLocationCallback) {
        if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
            ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            if (context is MainActivity) {
                ActivityCompat.requestPermissions(context, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION), 1000)
            }
            return
        }

        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                for (location in locationResult.locations) {
                    callback.onLocationResult(location)
                }
            }

            override fun onLocationAvailability(availability: LocationAvailability) {
                if (!availability.isLocationAvailable) {
                    callback.onLocationError("Location unavailable")
                }
            }
        }

        fusedLocationClient.requestLocationUpdates(locationRequest!!, locationCallback!!, null)
    }

    fun stopLocationUpdates() {
        locationCallback?.let { fusedLocationClient.removeLocationUpdates(it) }
    }
}

次にMainActivityを変更します

MainActivity.kt
class MainActivity : ComponentActivity() {
    private lateinit var gpsLocationManager: GPSLocationManager
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        gpsLocationManager = GPSLocationManager(this)


        enableEdgeToEdge()
        setContent {
            GetGPSTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    GPSViewer(
                        modifier = Modifier.padding(innerPadding),
                        gpsLocationManager
                    )
                }
            }
        }
    }
    override fun onDestroy() {
        super.onDestroy()
        gpsLocationManager.stopLocationUpdates()
    }
}

@Composable
fun GPSViewer(modifier: Modifier = Modifier,gpsLocationManager: GPSLocationManager) {
    var latitude by remember { mutableStateOf("") }
    var longitude by remember { mutableStateOf("") }
    Column (modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally, 
        verticalArrangement = Arrangement.Center 
    ){
        Text(
            text = "緯度:$latitude\n経度:$longitude",
            modifier = modifier
        )
        //---ここが変わってるよ---
        Button(onClick = {
            gpsLocationManager.startLocationUpdates(object : GPSLocationManager.MyLocationCallback {
        //getLastLocationをstartLocationUpdatesにしてね
        //-------------------
                override fun onLocationResult(location: Location?) {
                    if (location != null) {
                        latitude = location.latitude.toString()
                        longitude = location.longitude.toString()
                    }
                }

                override fun onLocationError(error: String) {
                    // エラー処理
                }
            })
        }) {
            Text(
                text = "位置情報取得",
            )
        }
    }
}

解説

startLocationUpdates

位置情報が変化した際に値を返すコールバック関数です
Builder(Priority.PRIORITY_HIGH_ACCURACY, getRate)最新の位置情報の取得頻度
setMinUpdateIntervalMillis(minRate)で最新の位置情報の更新頻度を設定しています

この関数だと位置情報の更新と取得の両方を行なっているので最新の位置情報を定期的に取得することができます。

stopLocationUpdates

位置情報の取得をやめる関数です。
今回はonDestroyのライフサイクルで実行することでアプリを停止した際に位置情報の取得も停止しています。


これを実行するとボタンを押した後から約10秒ごとに位置情報に変化があった際位置情報を取得するようになっています。
getRate,minRateを変更することで位置情報を取得する頻度を変更できます。

おわりに

今回は簡単に位置情報の取得を行いました。
ロック画面等でも位置情報を取得したい場合ではフォアグラウンドサービスにする必要があるので注意してください。
フォアグラウンドサービスににました↓
https://zenn.dev/nenfa/articles/656c9aee41729d

変更履歴

  • 2024_09_09 libs.versions.tomlを書き忘れてたので修正
  • 2024_09_27 フォアグラウンドサービス化をした記事のリンクを追加

Discussion