How to get your location in Jetpack Compose

Mun Bonecci
6 min readFeb 2, 2024

--

In this short tutorial, we will see how to obtain your location and request the necessary permissions in Jetpack Compose.

If you want to know more about permissions, go to this Page and more about location in this Android Developers Location Page

To do this, we will need to add the following dependencies in build.gradle(Module:app).

dependencies {
implementation("com.google.android.gms:play-services-location:21.1.0")
implementation("com.google.accompanist:accompanist-permissions:0.35.0-alpha")
}

First, we need to do the following to request location permissions.

Add the necessary permissions in Manifest.xml

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

Use this RequestLocationPermission Composable function to launch the location permissions.

/**
* Composable function to request location permissions and handle different scenarios.
*
* @param onPermissionGranted Callback to be executed when all requested permissions are granted.
* @param onPermissionDenied Callback to be executed when any requested permission is denied.
* @param onPermissionsRevoked Callback to be executed when previously granted permissions are revoked.
*/
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun RequestLocationPermission(
onPermissionGranted: () -> Unit,
onPermissionDenied: () -> Unit,
onPermissionsRevoked: () -> Unit
) {
// Initialize the state for managing multiple location permissions.
val permissionState = rememberMultiplePermissionsState(
listOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
)
)

// Use LaunchedEffect to handle permissions logic when the composition is launched.
LaunchedEffect(key1 = permissionState) {
// Check if all previously granted permissions are revoked.
val allPermissionsRevoked =
permissionState.permissions.size == permissionState.revokedPermissions.size

// Filter permissions that need to be requested.
val permissionsToRequest = permissionState.permissions.filter {
!it.status.isGranted
}

// If there are permissions to request, launch the permission request.
if (permissionsToRequest.isNotEmpty()) permissionState.launchMultiplePermissionRequest()

// Execute callbacks based on permission status.
if (allPermissionsRevoked) {
onPermissionsRevoked()
} else {
if (permissionState.allPermissionsGranted) {
onPermissionGranted()
} else {
onPermissionDenied()
}
}
}
}

Annotations:

  • @OptIn(ExperimentalPermissionsApi::class): Indicates that the function is using an experimental feature related to permissions. This is useful for developers to be aware of experimental features.

Composable Function:

  • @Composable: Indicates that the function is a composable function, allowing it to be used in Jetpack Compose UI.

Function Implementation:

  • The function initializes a permissionState using Jetpack Compose's rememberMultiplePermissionsState, specifying the location-related permissions.
  • It uses LaunchedEffect to handle the logic when the composable is launched. This is a Compose function that performs side effects when the key dependencies change.
  • The function checks if previously granted permissions are revoked, filters permissions that need to be requested, launches the permission request if necessary, and executes callbacks based on permission status.

Choose the best location estimate.

FusedLocationProviderClientprovides several methods to retrieve device location information. Choose one of the following options, depending on your app's use case:

  • getLastLocation()Get a faster location estimate and minimize battery usage that can be attributed to your app. However, the location information could be out of date if no other customers have actively used the location recently.
  • getCurrentLocation()you get a more up-to-date and accurate location, and in a more consistent manner. However, this method may generate an active location estimate on the device.
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient

//Initialize it where you need it
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)

Now, to get the last location from the device.

Use this getLastUserLocation function.

/**
* Retrieves the last known user location asynchronously.
*
* @param onGetLastLocationSuccess Callback function invoked when the location is successfully retrieved.
* It provides a Pair representing latitude and longitude.
* @param onGetLastLocationFailed Callback function invoked when an error occurs while retrieving the location.
* It provides the Exception that occurred.
*/
@SuppressLint("MissingPermission")
private fun getLastUserLocation(
onGetLastLocationSuccess: (Pair<Double, Double>) -> Unit,
onGetLastLocationFailed: (Exception) -> Unit
) {
// Check if location permissions are granted
if (areLocationPermissionsGranted()) {
// Retrieve the last known location
fusedLocationProviderClient.lastLocation
.addOnSuccessListener { location ->
location?.let {
// If location is not null, invoke the success callback with latitude and longitude
onGetLastLocationSuccess(Pair(it.latitude, it.longitude))
}
}
.addOnFailureListener { exception ->
// If an error occurs, invoke the failure callback with the exception
onGetLastLocationFailed(exception)
}
}
}

Explanation:

@SuppressLint("MissingPermission"):

  • This annotation suppresses lint warnings related to missing location permissions. It suggests that the function assumes the necessary permissions are already checked or handled elsewhere.

Function Parameters:

  • onGetLastLocationSuccess: A callback function invoked when the location is successfully retrieved. It takes a Pair<Double, Double> representing latitude and longitude.
  • onGetLastLocationFailed: A callback function invoked when an error occurs while retrieving the location. It takes an Exception parameter.

Location Permission Check:

  • areLocationPermissionsGranted(): It's a function (presumably elsewhere in the code) that checks if location permissions are granted.

Asynchronous Location Retrieval:

  • fusedLocationProviderClient.lastLocation: It's an asynchronous call to retrieve the last known location using the Fused Location Provider.

Callbacks:

  • addOnSuccessListener: If the location retrieval is successful, it checks if the location is not null and invokes the success callback with latitude and longitude.
  • addOnFailureListener: If an error occurs during location retrieval, it invokes the failure callback with the exception.

In summary, this function encapsulates the process of asynchronously retrieving the last known user location, handling permissions, and providing callbacks for success and failure scenarios. It’s designed to be used in a context where the necessary location permissions are assumed to be granted.

To retrieve the current location of the device.

Simply employ the getCurrentUserLocation function for this purpose.

/**
* Retrieves the current user location asynchronously.
*
* @param onGetCurrentLocationSuccess Callback function invoked when the current location is successfully retrieved.
* It provides a Pair representing latitude and longitude.
* @param onGetCurrentLocationFailed Callback function invoked when an error occurs while retrieving the current location.
* It provides the Exception that occurred.
* @param priority Indicates the desired accuracy of the location retrieval. Default is high accuracy.
* If set to false, it uses balanced power accuracy.
*/
@SuppressLint("MissingPermission")
private fun getCurrentLocation(
onGetCurrentLocationSuccess: (Pair<Double, Double>) -> Unit,
onGetCurrentLocationFailed: (Exception) -> Unit,
priority: Boolean = true
) {
// Determine the accuracy priority based on the 'priority' parameter
val accuracy = if (priority) Priority.PRIORITY_HIGH_ACCURACY
else Priority.PRIORITY_BALANCED_POWER_ACCURACY

// Check if location permissions are granted
if (areLocationPermissionsGranted()) {
// Retrieve the current location asynchronously
fusedLocationProviderClient.getCurrentLocation(
accuracy, CancellationTokenSource().token,
).addOnSuccessListener { location ->
location?.let {
// If location is not null, invoke the success callback with latitude and longitude
onGetCurrentLocationSuccess(Pair(it.latitude, it.longitude))
}
}.addOnFailureListener { exception ->
// If an error occurs, invoke the failure callback with the exception
onGetCurrentLocationFailed(exception)
}
}
}

Explanation:

  • The function getCurrentLocation is designed to asynchronously retrieve the current user location.
  • It takes three parameters:
  • onGetCurrentLocationSuccess: A callback function invoked when the current location is successfully retrieved. It provides a Pair representing latitude and longitude.
  • onGetCurrentLocationFailed: A callback function invoked when an error occurs during the location retrieval. It provides the Exception that occurred.
  • priority: A boolean parameter indicating the desired accuracy of the location retrieval. The default is high accuracy, and if set to false, it uses balanced power accuracy.
  • The @SuppressLint("MissingPermission") annotation is used to suppress lint warnings about missing permissions. This is applied because the function assumes that location permissions are already checked before calling it.
  • The function determines the accuracy priority based on the priority parameter.
  • It checks if location permissions are granted using the areLocationPermissionsGranted function.
  • It then retrieves the current location asynchronously using fusedLocationProviderClient.getCurrentLocation.
  • Success and failure listeners are attached to handle the outcomes, invoking the appropriate callback functions with the relevant information.

Overall, this function encapsulates the logic for obtaining the current user location with flexibility in accuracy requirements.

With the following code, you can check if location permissions are enabled.

/**
* Checks if location permissions are granted.
*
* @return true if both ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions are granted; false otherwise.
*/
private fun areLocationPermissionsGranted(): Boolean {
return (ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED)
}

Code Explanation:

  • The function uses ActivityCompat.checkSelfPermission to check whether the specified location permissions are granted or not.
  • It checks both ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions.
  • If both permissions are granted (PackageManager.PERMISSION_GRANTED), the function returns true, indicating that location permissions are granted.
  • If any of the permissions is not granted, the function returns false.

You must take into consideration the following.

The location object can be nullin the following situations:

  • Location is disabled in device settings. The result could be nulleven if the most recent location was already obtained, because when location is disabled, the cache is also cleared.
  • The device never recorded its location, which could be the case for a new device or a device that has been reset to factory settings.
  • Google Play Services was restarted on the device, and no active combined location provider clients requested location after the services were restarted. To avoid this situation, you can create a new client and request location updates on your own. For more information, see How to receive location updates .
Location Precise and Approximate modal
Logs for the current location image

And that’s all for today, I hope you like this tutorial. I invite you to test and review the code more carefully in the following repository.

--

--

Mun Bonecci

As an Android developer, I'm passionate about evolving tech. I thrive on continuous learning, staying current with trends, and contributing in these fields.