Tracking device location for Android
The Mapbox Core Libraries for Android are a set of utilities, which help you with system permissions, device location, and connectivity within your Android project. These libraries help you with:
- Checking for, requesting, and responding to Android system location permissions.
- Checking for and responding to a change in the device's internet connectivity status.
- Retrieving a device's real-time location.
This tutorial will instruct you on how to add a Mapbox map to an Android app and then set up the Mapbox Core Libraries for Android to receive an update whenever a device's location changes. You will also use the Mapbox Maps SDK for Android's LocationComponent to display the device location icon. The information in a location change update includes the device's real-time coordinates, which can be helpful for your project.
By the end of this tutorial, you will have an app that displays an Android system toast with the latest device location coordinates, whenever the Core library delivers a location update. Rather than showing a toast, you could use the coordinates how you'd like:
Getting started
Start by creating a new project in Android Studio and initializing a MapView. There are five files you'll be working with in your Android Studio project to set up a Mapbox map and add custom data to be styled using data-driven styling. The five files you'll be working with include:
- build.gradle: Android Studio uses a toolkit called Gradle to compile resources and source code into an APK. The
build.gradlefile is used to configure the build and list dependencies, including the Mapbox Maps SDK for Android. - AndroidManifest.xml: The
AndroidManifest.xmlfile is where you'll describe components of the application, including Mapbox-related permissions. - activity_main.xml: The
activity_main.xmlfile is where you'll set the properties for yourMapView(for example, the center of the map view, the zoom level, and the map style used). - strings.xml: You'll store your access token in the
strings.xmlfile. - MainActivity.java:
MainActivity.javais a Java file where you'll specify Mapbox-specific interactions.
// in addition to the rest of your build.gradle contents
// you should include the following repository and dependency
repositories {
mavenCentral()
}
dependencies {
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:9.5.0'
}
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:mapbox="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"> <com.mapbox.mapboxsdk.maps.MapViewandroid:id="@+id/mapView"android:layout_width="0dp"android:layout_height="0dp"mapbox:layout_constraintBottom_toBottomOf="parent"mapbox:layout_constraintEnd_toEndOf="parent"mapbox:layout_constraintStart_toStartOf="parent"mapbox:layout_constraintTop_toTopOf="parent"mapbox:mapbox_cameraTargetLat="36.16266"mapbox:mapbox_cameraTargetLng="-86.78160"mapbox:mapbox_cameraZoom="12"/> </androidx.constraintlayout.widget.ConstraintLayout><string name="access_token" translatable="false">YOUR_MAPBOX_ACCESS_TOKEN</string>
import android.annotation.SuppressLint;import android.location.Location;import android.util.Log;import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import androidx.annotation.NonNull;// Classes needed to initialize the mapimport com.mapbox.mapboxsdk.Mapbox;import com.mapbox.mapboxsdk.maps.MapView;import com.mapbox.mapboxsdk.maps.MapboxMap;import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;import com.mapbox.mapboxsdk.maps.Style;show hidden lines/*** Use the Mapbox Core Library to listen to device location updates*/public class MainActivity extends AppCompatActivity implementsOnMapReadyCallback, PermissionsListener {// Variables needed to initialize a mapprivate MapboxMap mapboxMap;private MapView mapView;show hidden lines@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); // Mapbox access token is configured here. This needs to be called either in your application// object or in the same activity which contains the mapview.Mapbox.getInstance(this, getString(R.string.mapbox_access_token)); // This contains the MapView in XML and needs to be called after the access token is configured.setContentView(R.layout.activity_main); mapView = findViewById(R.id.mapView);mapView.onCreate(savedInstanceState);mapView.getMapAsync(this);} @Overridepublic void onMapReady(@NonNull final MapboxMap mapboxMap) {this.mapboxMap = mapboxMap; mapboxMap.setStyle(Style.TRAFFIC_NIGHT,new Style.OnStyleLoaded() {@Overridepublic void onStyleLoaded(@NonNull Style style) {show hidden lines}});}show hidden lines@Overrideprotected void onStart() {super.onStart();mapView.onStart();} @Overrideprotected void onResume() {super.onResume();mapView.onResume();} @Overrideprotected void onPause() {super.onPause();mapView.onPause();} @Overrideprotected void onStop() {super.onStop();mapView.onStop();} @Overrideprotected void onSaveInstanceState(Bundle outState) {super.onSaveInstanceState(outState);mapView.onSaveInstanceState(outState);} @Overrideprotected void onDestroy() {super.onDestroy();show hidden linesmapView.onDestroy();} @Overridepublic void onLowMemory() {super.onLowMemory();mapView.onLowMemory();}show hidden linesYou can learn how to set up an Android Studio project with the Maps SDK for Android in the First steps with the Mapbox Maps SDK for Android guide.
Run your application, and you should see a map with the Mapbox Traffic Night style centered on Nashville, Tennessee, in the United States of America.
Handle location permissions
The Android system requires an app to be approved for device location tracking. The Mapbox Core Library helps to check for, request, and respond to the location permission request.
Using our static check, which returns a boolean, you can check if location permissions have already been requested. To make a location permission request, first implement the PermissionsListener interface.
show hidden lines// Classes needed to handle location permissions import com.mapbox.android.core.permissions.PermissionsListener;import com.mapbox.android.core.permissions.PermissionsManager;import java.util.List;show hidden lines// Variables needed to handle location permissions private PermissionsManager permissionsManager;show hidden lines/*** Initialize the Maps SDK's LocationComponent*/@SuppressWarnings( {"MissingPermission"})private void enableLocationComponent(@NonNull Style loadedMapStyle) {show hidden lines}show hidden linesOverride the onExplanationNeeded() method once you implement the PermissionsListener interface.
Add a String resource titled user_location_permission_explanation in your strings.xml file, which will display if and when you need to provide further explanation for your location permission request.
<string name="user_location_permission_explanation">EXPLANATION_HERE</string>
show hidden lines@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);} @Overridepublic void onExplanationNeeded(List<String> permissionsToExplain) {Toast.makeText(this, R.string.user_location_permission_explanation, Toast.LENGTH_LONG).show();}show hidden linesThe other method to override is onPermissionResult(). This convenient method will deliver you a true/false boolean value, which tells you whether a user accepted or denied the app's request to gain access to the device's location. Initialize the Maps SDK's LocationComponent once the location permission has been granted.
show hidden lines@Overridepublic void onPermissionResult(boolean granted) {if (granted) {if (mapboxMap.getStyle() != null) {show hidden lines}} else {Toast.makeText(this, R.string.user_location_permission_not_granted, Toast.LENGTH_LONG).show();finish();}}show hidden linesTo start a permission check, create a new PermissionManager and call requestLocationPermissions()
show hidden lines// Check if permissions are enabled and if not requestif (PermissionsManager.areLocationPermissionsGranted(this)) {show hidden lines} else {permissionsManager = new PermissionsManager(this);permissionsManager.requestLocationPermissions(this);}show hidden linesInitialize the LocationEngine
Now that permissions have been handled, a LocationEngine object must be created and used.
Building a LocationEngineRequest is next. In the request, define:
- how many milliseconds you'd like to pass between location updates.
- how accurate you want the location updates to be.
- the maximum wait time in milliseconds for location updates. Locations determined at intervals but delivered in batch based on wait time. Batching is not supported by all engines.
show hidden lines// Classes needed to add the location engineimport com.mapbox.android.core.location.LocationEngine;import com.mapbox.android.core.location.LocationEngineCallback;import com.mapbox.android.core.location.LocationEngineProvider;import com.mapbox.android.core.location.LocationEngineRequest;import com.mapbox.android.core.location.LocationEngineResult;import java.lang.ref.WeakReference;show hidden lines// Variables needed to add the location engineprivate LocationEngine locationEngine;private long DEFAULT_INTERVAL_IN_MILLISECONDS = 1000L;private long DEFAULT_MAX_WAIT_TIME = DEFAULT_INTERVAL_IN_MILLISECONDS * 5;show hidden lines/*** Set up the LocationEngine and the parameters for querying the device's location*/@SuppressLint("MissingPermission")private void initLocationEngine() {locationEngine = LocationEngineProvider.getBestLocationEngine(this); LocationEngineRequest request = new LocationEngineRequest.Builder(DEFAULT_INTERVAL_IN_MILLISECONDS).setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY).setMaxWaitTime(DEFAULT_MAX_WAIT_TIME).build(); locationEngine.requestLocationUpdates(request, callback, getMainLooper());locationEngine.getLastLocation(callback);}show hidden linesYou'll also want to add the following to onDestroy() to prevent leaks:
show hidden lines// Prevent leaksif (locationEngine != null) {locationEngine.removeLocationUpdates(callback);}mapView.onDestroy();show hidden linesEnable the LocationComponent
This section is optional. You can skip this section if you don't care about showing the location puck on the map.
Thanks to the PermissionsManager.areLocationPermissionsGranted(this) boolean check, you now know that the location permission has been granted. You also know that the LocationEngine has been initialized. Now you can confidently initialize the Maps SDK's LocationComponent. The LocationComponent shows the device location "puck" icon on the map. You don't have to show the device location icon on the map to progress through this help guide. Skip this section if you don't care about showing the location puck on the map
The LocationComponent has COMPASS as its RenderMode, which means an arrow will be shown outside the location icon to display the device's compass bearing. The arrow indicates which direction the device is pointed towards. There are other RenderMode choices if you do not want COMPASS.
The LocationComponent has TRACKING as its CameraMode, which means that the map camera will follow the device's location. There are other CameraMode options though.
show hidden lines// Classes needed to add the location componentimport com.mapbox.mapboxsdk.location.LocationComponent;import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions;import com.mapbox.mapboxsdk.location.modes.CameraMode;import com.mapbox.mapboxsdk.location.modes.RenderMode;show hidden linesenableLocationComponent(style);show hidden lines// Get an instance of the componentLocationComponent locationComponent = mapboxMap.getLocationComponent(); // Set the LocationComponent activation optionsLocationComponentActivationOptions locationComponentActivationOptions =LocationComponentActivationOptions.builder(this, loadedMapStyle).useDefaultLocationEngine(false).build(); // Activate with the LocationComponentActivationOptions objectlocationComponent.activateLocationComponent(locationComponentActivationOptions); // Enable to make component visiblelocationComponent.setLocationComponentEnabled(true); // Set the component's camera modelocationComponent.setCameraMode(CameraMode.TRACKING); // Set the component's render modelocationComponent.setRenderMode(RenderMode.COMPASS); initLocationEngine();show hidden linesenableLocationComponent(mapboxMap.getStyle());show hidden linesListen to location updates
The last step is to create a location update interface callback to listen to location updates from the Mapbox Core Libraries for Android.
Create a class that implements LocationEngineCallback<LocationEngineResult>. The LocationEngineCallback<LocationEngineResult> interface is a part of the Core Libraries. Make sure the class requires Android system Activity as a constructor parameter. This new class should be created because a LocationEngine memory leak is possible if the activity/fragment directly implements the LocationEngineCallback<LocationEngineResult>. The WeakReference setup avoids the leak.
Implementing LocationEngineCallback<LocationEngineResult> requires you to override the onSuccess() and onFailure() methods.
show hidden lines// Variables needed to listen to location updatesprivate MainActivityLocationCallback callback = new MainActivityLocationCallback(this);show hidden linesprivate static class MainActivityLocationCallbackimplements LocationEngineCallback<LocationEngineResult> { private final WeakReference<MainActivity> activityWeakReference; MainActivityLocationCallback(MainActivity activity) {this.activityWeakReference = new WeakReference<>(activity);} /*** The LocationEngineCallback interface's method which fires when the device's location has changed.** @param result the LocationEngineResult object which has the last known location within it.*/@Overridepublic void onSuccess(LocationEngineResult result) {MainActivity activity = activityWeakReference.get(); if (activity != null) {Location location = result.getLastLocation(); if (location == null) {return;} // Create a Toast which displays the new location's coordinatesToast.makeText(activity, String.format(activity.getString(R.string.new_location),String.valueOf(result.getLastLocation().getLatitude()), String.valueOf(result.getLastLocation().getLongitude())),Toast.LENGTH_SHORT).show(); // Pass the new location to the Maps SDK's LocationComponentif (activity.mapboxMap != null && result.getLastLocation() != null) {activity.mapboxMap.getLocationComponent().forceLocationUpdate(result.getLastLocation());}}} /*** The LocationEngineCallback interface's method which fires when the device's location can not be captured** @param exception the exception message*/@Overridepublic void onFailure(@NonNull Exception exception) {Log.d("LocationChangeActivity", exception.getLocalizedMessage());MainActivity activity = activityWeakReference.get();if (activity != null) {Toast.makeText(activity, exception.getLocalizedMessage(),Toast.LENGTH_SHORT).show();}}}show hidden linesOnSuccess() runs whenever the Mapbox Core Libraries identifies a change in the device's location. result.getLastLocation() gives you a Location object and that object has the latitude and longitude values. Now you can display the values in your app's UI, save it in memory, send it to your backend server, or use the device location information how you'd like.
Finished product
You have set up code to be notified of device location updates. The screenshot below shows Nashville, but in reality, the map camera will move to wherever your device is if you're still using TRACKING as the LocationComponent's CameraMode.
import android.annotation.SuppressLint;import android.location.Location;import android.util.Log;import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import androidx.annotation.NonNull;// Classes needed to initialize the mapimport com.mapbox.mapboxsdk.Mapbox;import com.mapbox.mapboxsdk.maps.MapView;import com.mapbox.mapboxsdk.maps.MapboxMap;import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;import com.mapbox.mapboxsdk.maps.Style;// Classes needed to handle location permissions import com.mapbox.android.core.permissions.PermissionsListener;import com.mapbox.android.core.permissions.PermissionsManager;import java.util.List;// Classes needed to add the location engineimport com.mapbox.android.core.location.LocationEngine;import com.mapbox.android.core.location.LocationEngineCallback;import com.mapbox.android.core.location.LocationEngineProvider;import com.mapbox.android.core.location.LocationEngineRequest;import com.mapbox.android.core.location.LocationEngineResult;import java.lang.ref.WeakReference;// Classes needed to add the location componentimport com.mapbox.mapboxsdk.location.LocationComponent;import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions;import com.mapbox.mapboxsdk.location.modes.CameraMode;import com.mapbox.mapboxsdk.location.modes.RenderMode; /*** Use the Mapbox Core Library to listen to device location updates*/public class MainActivity extends AppCompatActivity implementsOnMapReadyCallback, PermissionsListener {// Variables needed to initialize a mapprivate MapboxMap mapboxMap;private MapView mapView;// Variables needed to handle location permissions private PermissionsManager permissionsManager;// Variables needed to add the location engineprivate LocationEngine locationEngine;private long DEFAULT_INTERVAL_IN_MILLISECONDS = 1000L;private long DEFAULT_MAX_WAIT_TIME = DEFAULT_INTERVAL_IN_MILLISECONDS * 5;// Variables needed to listen to location updatesprivate MainActivityLocationCallback callback = new MainActivityLocationCallback(this); @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); // Mapbox access token is configured here. This needs to be called either in your application// object or in the same activity which contains the mapview.Mapbox.getInstance(this, getString(R.string.mapbox_access_token)); // This contains the MapView in XML and needs to be called after the access token is configured.setContentView(R.layout.activity_main); mapView = findViewById(R.id.mapView);mapView.onCreate(savedInstanceState);mapView.getMapAsync(this);} @Overridepublic void onMapReady(@NonNull final MapboxMap mapboxMap) {this.mapboxMap = mapboxMap; mapboxMap.setStyle(Style.TRAFFIC_NIGHT,new Style.OnStyleLoaded() {@Overridepublic void onStyleLoaded(@NonNull Style style) {enableLocationComponent(style);}});} /*** Initialize the Maps SDK's LocationComponent*/@SuppressWarnings( {"MissingPermission"})private void enableLocationComponent(@NonNull Style loadedMapStyle) {// Check if permissions are enabled and if not requestif (PermissionsManager.areLocationPermissionsGranted(this)) { // Get an instance of the componentLocationComponent locationComponent = mapboxMap.getLocationComponent(); // Set the LocationComponent activation optionsLocationComponentActivationOptions locationComponentActivationOptions =LocationComponentActivationOptions.builder(this, loadedMapStyle).useDefaultLocationEngine(false).build(); // Activate with the LocationComponentActivationOptions objectlocationComponent.activateLocationComponent(locationComponentActivationOptions); // Enable to make component visiblelocationComponent.setLocationComponentEnabled(true); // Set the component's camera modelocationComponent.setCameraMode(CameraMode.TRACKING); // Set the component's render modelocationComponent.setRenderMode(RenderMode.COMPASS); initLocationEngine();} else {permissionsManager = new PermissionsManager(this);permissionsManager.requestLocationPermissions(this);}} /*** Set up the LocationEngine and the parameters for querying the device's location*/@SuppressLint("MissingPermission")private void initLocationEngine() {locationEngine = LocationEngineProvider.getBestLocationEngine(this); LocationEngineRequest request = new LocationEngineRequest.Builder(DEFAULT_INTERVAL_IN_MILLISECONDS).setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY).setMaxWaitTime(DEFAULT_MAX_WAIT_TIME).build(); locationEngine.requestLocationUpdates(request, callback, getMainLooper());locationEngine.getLastLocation(callback);} @Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);} @Overridepublic void onExplanationNeeded(List<String> permissionsToExplain) {Toast.makeText(this, R.string.user_location_permission_explanation, Toast.LENGTH_LONG).show();} @Overridepublic void onPermissionResult(boolean granted) {if (granted) {if (mapboxMap.getStyle() != null) {enableLocationComponent(mapboxMap.getStyle());}} else {Toast.makeText(this, R.string.user_location_permission_not_granted, Toast.LENGTH_LONG).show();finish();}} private static class MainActivityLocationCallbackimplements LocationEngineCallback<LocationEngineResult> { private final WeakReference<MainActivity> activityWeakReference; MainActivityLocationCallback(MainActivity activity) {this.activityWeakReference = new WeakReference<>(activity);} /*** The LocationEngineCallback interface's method which fires when the device's location has changed.** @param result the LocationEngineResult object which has the last known location within it.*/@Overridepublic void onSuccess(LocationEngineResult result) {MainActivity activity = activityWeakReference.get(); if (activity != null) {Location location = result.getLastLocation(); if (location == null) {return;} // Create a Toast which displays the new location's coordinatesToast.makeText(activity, String.format(activity.getString(R.string.new_location),String.valueOf(result.getLastLocation().getLatitude()), String.valueOf(result.getLastLocation().getLongitude())),Toast.LENGTH_SHORT).show(); // Pass the new location to the Maps SDK's LocationComponentif (activity.mapboxMap != null && result.getLastLocation() != null) {activity.mapboxMap.getLocationComponent().forceLocationUpdate(result.getLastLocation());}}} /*** The LocationEngineCallback interface's method which fires when the device's location can not be captured** @param exception the exception message*/@Overridepublic void onFailure(@NonNull Exception exception) {Log.d("LocationChangeActivity", exception.getLocalizedMessage());MainActivity activity = activityWeakReference.get();if (activity != null) {Toast.makeText(activity, exception.getLocalizedMessage(),Toast.LENGTH_SHORT).show();}}} @Overrideprotected void onStart() {super.onStart();mapView.onStart();} @Overrideprotected void onResume() {super.onResume();mapView.onResume();} @Overrideprotected void onPause() {super.onPause();mapView.onPause();} @Overrideprotected void onStop() {super.onStop();mapView.onStop();} @Overrideprotected void onSaveInstanceState(Bundle outState) {super.onSaveInstanceState(outState);mapView.onSaveInstanceState(outState);} @Overrideprotected void onDestroy() {super.onDestroy();// Prevent leaksif (locationEngine != null) {locationEngine.removeLocationUpdates(callback);}mapView.onDestroy();} @Overridepublic void onLowMemory() {super.onLowMemory();mapView.onLowMemory();}}Next steps
There are many possibilities when using the LocationComponent. Explore the Android demo app's location-related examples to see how to show a device's location in an Android fragment, to customize the LocationComponent icon, and much more.