I am trying to understand how to use Location services in an Android App developed using Jetpack Compose. Following some tutorials I came across; I have written the following code component to understand how to achieve this.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
GetLocationTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
LocationDisplay()
}
}
}
}
}
@OptIn(ExperimentalPermissionsApi::class)
@SuppressLint("MissingPermission")
@Composable
private fun LocationDisplay() {
var locationText by remember { mutableStateOf("")}
val locationPermissionsState = rememberMultiplePermissionsState(
listOf(
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION,
)
)
if (locationPermissionsState.allPermissionsGranted) {
Column {
Text(locationText)
Button(onClick = { /*TODO*/ }) {
Text("Stop")
}
}
val appContext = LocalContext.current.applicationContext
val fusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(appContext)
fusedLocationProviderClient.lastLocation
.addOnSuccessListener {location ->
location?.let {locationText = "Location ${it.latitude}, ${it.longitude}"}
Log.d("TAG", locationText)
}
}
else {
Column {
Button(
onClick = { locationPermissionsState.launchMultiplePermissionRequest() }
) { Text("Request permissions") }
}
}
}
As I understood this approach subscribes a lambda function to capture location updates and update a Text composable with the updated value. Therefore, the location updates continue once started.
I am trying to understand how I can query the location once instead of registering a listener. Is this possible? Alternatively, if I use the same approach, is there a way to de-register the callback once a valid reading is obtained?
I have tried to look at other methods provided by fusedLocationProviderClient.lastLocation hoping to see a function that would removeOnSuccessListner. I could not find out a way to do this.
I also tried to see if I can use a co-routine to await() for a location result. I did not succeed in getting this approach to work.
You can use something like this to get the location only once:
internal object LocationService {
suspend fun getCurrentLocation(context: Context): Location {
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
when {
!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) -> throw LocationServiceException.LocationDisabledException()
!locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) -> throw LocationServiceException.NoNetworkEnabledException()
else -> {
val locationProvider = LocationServices.getFusedLocationProviderClient(context)
val request = CurrentLocationRequest.Builder()
.setPriority(Priority.PRIORITY_BALANCED_POWER_ACCURACY)
.build()
runCatching {
val location = if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
throw LocationServiceException.MissingPermissionException()
} else {
locationProvider.getCurrentLocation(request, null).await()
}
return location
}.getOrElse {
throw LocationServiceException.UnknownException(stace = it.stackTraceToString())
}
}
}
}
sealed class LocationServiceException : Exception() {
class MissingPermissionException : LocationServiceException()
class LocationDisabledException : LocationServiceException()
class NoNetworkEnabledException : LocationServiceException()
class UnknownException(val stace: String) :LocationServiceException()
}
}
You can simply call this object to get the location.