Search code examples
androidkotlinandroid-locationandroid-fusedlocation

On a Jetpack Compose application, is there a way to use LocationServices.getFusedLocationProviderClient to query a location value once


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.


Solution

  • 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.