Introduction
Location awareness is becoming an essential part of many successful mobile applications. Whether you're building a fitness tracker, a navigation app, a ride-sharing app, a weather app, an augmented reality experience or a service that connects users based on proximity, incorporating location functionality can significantly enhance your app's value and user experience.
This article helps you leverage the Location API in Android, providing a step-by-step approach to accessing the user's location, handling permissions, and setting up real-time location updates. By the end of this tutorial, you'll be well-equipped to integrate location-based features seamlessly into your Android projects.
Setting up your project to use the Location API
If you don't already have a project set up, follow the following steps:
Open Android Studio.
Go to File > New > New Project.
Select Basic Views Activity
Enter a name for your app
Click Finish
Open your app/build.gradle
file and add the following dependency
dependencies {
//Location
implementation 'com.google.android.gms:play-services-location:20.0.0'
}
If you use the Gradle Kotlin DSL, add the following to the libs.versions.toml
file
[versions]
...
playlocation = "21.3.0"
[libraries]
...
play-location = { group = "com.google.android.gms", name = "play-services-location", version.ref = "playlocation"}
[plugins]
...
And in the app/build.gradle.kts
file, add this
dependencies {
...
implementation(libs.play.location)
}
Click on Sync Project with Gradle Files so that the just added dependency is available for use in your code.
Add the following permissions to your AndroidManifest file
<manifest ...>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
</manifest>
- ACCESS_FINE_LOCATION: Grants access to the device's most precise location data (GPS, Wi-Fi, cell tower triangulation).
- ACCESS_COARSE_LOCATION: Provides a less accurate estimate using cell tower and Wi-Fi data (suitable for scenarios where exact coordinates aren't crucial).
Checking If your App has permission
Activity.checkSelfPermission()
checks if you have been granted a particular permission. To check if you have the Manifest.permission.ACCESS_FINE_LOCATION
permission, you would do:
ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
Let's use that to build a function that checks for the two permissions we need.
Add the following to the activity.
(Code snippets are presented first in Kotlin and then in Java)
private fun isLocationPermissionGranted(activity: Activity): Boolean {
return ActivityCompat.checkSelfPermission(
activity,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(
activity,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
private boolean isLocationPermissionGranted(Activity activity) {
return ActivityCompat.checkSelfPermission(activity, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(activity, android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
This function checks if your app has the permission to access the device's coarse or fine location.
Requesting Permission at runtime
In the activity where location is needed, you must request permission at runtime. We will use an ActivityResultLauncher
to request permission and listen for the response to our request. To indicate the kind of request, we have two options. ActivityResultContracts.RequestMultiplePermissions()
or ActivityResultContracts.RequestPermission()
. We will use the former because we have two permissions to request.
The next function requests for the needed permissions.
private fun requestLocationPermission(callback: (Boolean) -> Unit) {
val locationPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val fineLocationGranted: Boolean =
permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false)
val coarseLocationGranted: Boolean =
permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false)
if (fineLocationGranted || coarseLocationGranted) {
callback(true) //permission granted
} else {
callback(false) //permission denied
}
}
locationPermissionRequest.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
)
}
private void requestLocationPermission(CallbackListener<Boolean> callbackListener) {
ActivityResultLauncher<String[]> locationPermissionRequest = registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
permissions -> {
Boolean fineLocationGranted = permissions.getOrDefault(android.Manifest.permission.ACCESS_FINE_LOCATION, false);
Boolean coarseLocationGranted = permissions.getOrDefault(android.Manifest.permission.ACCESS_COARSE_LOCATION, false);
if (
fineLocationGranted != null && fineLocationGranted ||
coarseLocationGranted != null && coarseLocationGranted
) {
// Permission granted
callbackListener.onCallback(true);
} else {
// No location access granted.
callbackListener.onCallback(false);
}
});
locationPermissionRequest.launch(new String[]{
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION
});
}
The result, permissions
, is a Map
containing the permissions requested and whether they were granted. We look at the map for the two permissions requested. If either of them were granted, we're good. Else, we cannot use the Location API. You may present the user with a warning to let them know that some features may be unavailable or may not function properly since they have not granted the app the permission to access their device location.
Check Phone's Location Settings
Let's say the user granted the permission to access the device's location, we also need to check if the device's settings satisfy our requirements. For example, the device's location may be off. In this case, we cannot access the location.
First, we build a location request. This is our way of specifying our requirements.
Add the following field to your Activity
private val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000)
.build()
private final LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000)
.build();
We use SettingsClient.checkLocationSettings()
to check if our requirement is met. If the requirement is met, we can proceed to the next step. If it isn't met, we need to ask the user to update their setting.
For that, we need an ActivityResultLauncher
to launch the request and listen for the user's action.
Add this field to your Activity.
private val locationSettingsResult =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
when (result.resultCode) {
Activity.RESULT_OK -> {
//User has updated the device's setting
}
Activity.RESULT_CANCELED -> {
//Warn user that location is required for some features
}
}
}
private ActivityResultLauncher<IntentSenderRequest> locationSettingsResult;
public void onCreate(@Nullable Bundle savedInstanceState) {
locationSettingsResult = registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(), result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
//User has updated the device's setting
} else {
//Warn user that location is required for some features
}
});
}
This ActivityResultContracts
must be registered before the Activity starts, or the app will crash.
Putting it all together we have this function to check the device's location setting and ask the user to update their location settings if there is a need:
private fun checkPhoneLocationSettings(
activity: Activity,
locationSettingsResult: ActivityResultLauncher<IntentSenderRequest>,
callback: (Boolean) -> Unit
) {
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
val client = LocationServices.getSettingsClient(activity)
val task = client.checkLocationSettings(builder.build())
task.addOnSuccessListener { callback(true) }
task.addOnFailureListener { exception ->
if (exception is ResolvableApiException) {
try {
locationSettingsResult.launch(
IntentSenderRequest.Builder(exception.resolution).build()
)
} catch (sendEx: IntentSender.SendIntentException) {
// Ignore the error.
}
}
}
}
private void checkPhoneLocationSettings(
Activity activity,
ActivityResultLauncher<IntentSenderRequest> locationSettingsResult,
CallbackListener<Boolean> callbackListener) {
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
.addLocationRequest(locationRequest);
SettingsClient client = LocationServices.getSettingsClient(activity);
Task<LocationSettingsResponse> task = client.checkLocationSettings(builder.build());
task.addOnSuccessListener(activity, locationSettingsResponse -> {
//Location settings are fine. You can start listening for location
callbackListener.onCallback(true);
});
task.addOnFailureListener(activity, e -> {
if (e instanceof ResolvableApiException) {
locationSettingsResult.launch(new IntentSenderRequest.Builder(((ResolvableApiException) e).getResolution()).build());
}
});
}
At this point, we have what we need to check if we have location permission. If we don't we can request the permissions. After which we can check if the settings meet our location requirements. Let's package all of that into a function.
private fun setUpLocationComponentsAndGetLocation(activity: Activity) {
if (isLocationPermissionGranted(activity)) {
checkPhoneLocationSettings(activity) { isLocationSettingsOk ->
if (isLocationSettingsOk) {
//You can get last known location or request location update here because you have permission
//and the device settings meet your requirement.
} else {
//warn user
}
}
} else {
requestLocationPermission { permissionGranted ->
if (permissionGranted) {
//simply a recursive call
setUpLocationComponentsAndGetLocation(activity)
} else {
//warn user
}
}
}
}
private void setUpLocationComponentsAndGetLocation(Activity activity) {
if (isLocationPermissionGranted(activity)) {
checkPhoneLocationSettings(activity, locationSettingsResult, isLocationSettingsOk -> {
if (isLocationSettingsOk) {
//You can get last known location or request location update here because you have permission
//and the device settings meet your requirement.
} else {
//You may decide to warn users that some functions may not be available without permission
}
});
} else {
requestLocationPermission(permissionGranted -> {
if (permissionGranted) {
//simply a recursive call
setUpLocationComponentsAndGetLocation(activity);
} else {
//Warn user
}
});
}
}
Accessing Location
At this point, we can now access the device's location. FusedLocationProviderClient
is the main entry point for interacting with the Fused Location Provider - the Location API by Google for accessing user location. To begin, add this field to your Activity:
private val mFusedLocationProviderClient: FusedLocationProviderClient by lazy {
LocationServices.getFusedLocationProviderClient(this)
}
private FusedLocationProviderClient fusedLocationProviderClient;
@Override
protected void onStart() {
super.onStart();
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
}
There are two methods of concern to us in the FusedLocationProviderClient class:
FusedLocationProviderClient.getLastLocation()
retrieves the last known location while FusedLocationProviderClient.requestLocationUpdates()
listens for Location updates.
For the former, think of Weather apps that get your current city and display the weather in that area. For the latter, think of Bolt or Uber which needs to get updates on the driver and rider's location.
Retrieving the last known location
To retrieve the last known location of the device,
@SuppressLint("MissingPermission")
private fun getLastLocation() {
mFusedLocationProviderClient.lastLocation.addOnSuccessListener { location ->
if (location != null) {
// Logic to handle location object
Log.d("Location", "Long" + location.longitude)
Log.d("Location", "Lat" + location.latitude)
}
}
}
@SuppressLint("MissingPermission")
private void getLastLocation() {
fusedLocationProviderClient.getLastLocation().addOnSuccessListener(location -> {
// Got last known location. In some rare situations this can be null.
if (location != null) {
// Logic to handle location object
Log.d("Location", "Long" + location.getLongitude());
Log.d("Location", "Lat" + location.getLatitude());
}
});
}
Notice that you need to check for null because the last location may be null at some point.
Setting up Location Updates for Real-time tracking
This requires some more work compared to retrieving the last known location because we need to set up a callback and manage lifecycle.
For the callback, add this field to your Activity:
private val locationCallBack = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
Log.d("Location", "Long" + locationResult.lastLocation?.longitude)
Log.d("Location", "Lat" + locationResult.lastLocation?.latitude)
}
}
private final LocationCallback locationCallback = new LocationCallback() {
@Override
public void onLocationResult(@NonNull LocationResult locationResult) {
super.onLocationResult(locationResult);
if (locationResult.getLastLocation() != null) {
Log.d("Location", "Long: " + locationResult.getLastLocation().getLongitude());
Log.d("Location", "Lat: " + locationResult.getLastLocation().getLatitude());
}
}
};
Remember we created a LocationRequest
object when checking if the device setting meets our requirement. Well, we need the same object now.
Create this function in your Activity
@SuppressLint("MissingPermission")
private fun requestLocationUpdate(activity: Activity) {
if (isLocationPermissionGranted(activity)) {
mFusedLocationProviderClient.requestLocationUpdates(
locationRequest,
locationCallBack,
Looper.getMainLooper()
)
}
}
@SuppressLint("MissingPermission")
private void requestLocationUpdate(Activity activity) {
if (isLocationPermissionGranted(activity)) {
fusedLocationProviderClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
);
}
}
Connecting it all up
Now that we have the function to Get the last known location and request location updates, it is time to update our setUpLocationComponentsAndGetLocation()
from before. Add the new lines to the setUpLocationComponentsAndGetLocation()
function.
private fun setUpLocationComponentsAndGetLocation(activity: Activity) {
if (isLocationPermissionGranted(activity)) {
checkPhoneLocationSettings(activity) { isLocationSettingsOk ->
if (isLocationSettingsOk) {
getLastLocation() //Add only this line to get the last location
requestLocationUpdate(activity) //Add only this line, to request location updates
} else {
//warn user
}
}
} else {
requestLocationPermission { permissionGranted ->
if (permissionGranted) {
//simply a recursive call
setUpLocationComponentsAndGetLocation(activity)
} else {
//warn user
}
}
}
}
private void setUpLocationComponentsAndGetLocation(Activity activity) {
if (isLocationPermissionGranted(activity)) {
checkPhoneLocationSettings(activity, locationSettingsResult, isLocationSettingsOk -> {
if (isLocationSettingsOk) {
getLastLocation(); //Add only this line to get the last location
requestLocationUpdate(activity); //Add only this line, to request location updates
} else {
//You may decide to warn users that some functions may not be available without permission
}
});
} else {
requestLocationPermission(permissionGranted -> {
if (permissionGranted) {
//simply a recursive call
setUpLocationComponentsAndGetLocation(activity);
} else {
//Warn user
}
});
}
}
To get location or request location update, all you need do at this point is to call the setUpLocationComponentsAndGetLocation()
from the entry point of the Activity that needs location data. Usually, this is the onStart()
method.
override fun onStart() {
super.onStart()
setUpLocationComponentsAndGetLocation(this)
}
@Override
protected void onStart() {
super.onStart();
setUpLocationComponentsAndGetLocation(this);
}
Cleaning up after yourself
One final and important thing. When you no longer require the location updates, you need to unsubscribe. This most likely happens when the user navigates away from the Activity that requires the location update. It is a best practice to stop location updates by removing the location callback. This way, you can avoid unnecessarily draining the device battery.
override fun onStop() {
mFusedLocationProviderClient.removeLocationUpdates(locationCallBack)
super.onStop()
}
@Override
protected void onStop() {
fusedLocationProviderClient.removeLocationUpdates(locationCallback);
super.onStop();
}
Conclusion
Integrating location functionality into your Android applications can significantly enhance user experience and provide valuable features for a wide range of applications. By following this step-by-step guide, you now know how to access the user's location, handle permissions, set up real-time location updates, and ensure your app meets the necessary location settings requirements. Implementing these features will allow you to create more dynamic and contextually aware applications.
This guide helped you leverage the Android Location API effectively in your mobile applications. It discussed the process of:
- Integrating the Location API dependency within your project.
- Requesting and handling location permissions at runtime.
- Verifying that the device's location settings meet your app's requirements.
- Retrieving the user's last known location.
- Setting up real-time location updates.
- Stopping location updates to optimize battery usage.
By following the steps in this article, you can add location-based functionalities to enhance your Android apps, create more dynamic and contextually aware applications and deliver a superior user experience.
For a complete example, you can refer to the repository at https://github.com/olubunmialegbeleye/Location. Happy coding!
Top comments (0)