Tutorials
intermediate
Java

Tracking device location for Android

Prerequisite

Familiarity with Android Studio and Java.

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.gradle file is used to configure the build and list dependencies, including the Mapbox Maps SDK for Android.
  • AndroidManifest.xml: The AndroidManifest.xml file is where you'll describe components of the application, including Mapbox-related permissions.
  • activity_main.xml: The activity_main.xml file is where you'll set the properties for your MapView (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.xml file.
  • MainActivity.java: MainActivity.java is a Java file where you'll specify Mapbox-specific interactions.
build.gradle (App module)
// 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.1.0'

}
Manifest.xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns: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.MapView
android: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>
strings.xml
<string name="access_token" translatable="false">YOUR_MAPBOX_ACCESS_TOKEN</string>
MainActivity.java
show hidden lines
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 map
import 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 implements
OnMapReadyCallback, PermissionsListener {
// Variables needed to initialize a map
private MapboxMap mapboxMap;
private MapView mapView;
show hidden lines
@Override
protected 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.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);
}
@Override
public void onMapReady(@NonNull final MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
mapboxMap.setStyle(Style.TRAFFIC_NIGHT,
new Style.OnStyleLoaded() {
@Override
public void onStyleLoaded(@NonNull Style style) {
show hidden lines
}
});
}
show hidden lines
@Override
protected void onStart() {
super.onStart();
mapView.onStart();
}
@Override
protected void onResume() {
super.onResume();
mapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
@Override
protected void onDestroy() {
super.onDestroy();
show hidden lines
mapView.onDestroy();
}
@Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
show hidden lines

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

MainActivity.java
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 lines

Override 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>
MainActivity.java
show hidden lines
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onExplanationNeeded(List<String> permissionsToExplain) {
Toast.makeText(this, R.string.user_location_permission_explanation, Toast.LENGTH_LONG).show();
}
show hidden lines

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

MainActivity.java
show hidden lines
@Override
public 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 lines

To start a permission check, create a new PermissionManager and call requestLocationPermissions()

MainActivity.java
show hidden lines
// Check if permissions are enabled and if not request
if (PermissionsManager.areLocationPermissionsGranted(this)) {
show hidden lines
} else {
permissionsManager = new PermissionsManager(this);
permissionsManager.requestLocationPermissions(this);
}
show hidden lines

Initialize 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.
MainActivity.java
show hidden lines
// Classes needed to add the location engine
import 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 engine
private 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 lines

You'll also want to add the following to onDestroy() to prevent leaks:

MainActivity.java
show hidden lines
// Prevent leaks
if (locationEngine != null) {
locationEngine.removeLocationUpdates(callback);
}
mapView.onDestroy();
show hidden lines

Enable the LocationComponent

Note

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.

MainActivity.java
show hidden lines
// Classes needed to add the location component
import 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 lines
enableLocationComponent(style);
show hidden lines
// Get an instance of the component
LocationComponent locationComponent = mapboxMap.getLocationComponent();
// Set the LocationComponent activation options
LocationComponentActivationOptions locationComponentActivationOptions =
LocationComponentActivationOptions.builder(this, loadedMapStyle)
.useDefaultLocationEngine(false)
.build();
// Activate with the LocationComponentActivationOptions object
locationComponent.activateLocationComponent(locationComponentActivationOptions);
// Enable to make component visible
locationComponent.setLocationComponentEnabled(true);
// Set the component's camera mode
locationComponent.setCameraMode(CameraMode.TRACKING);
// Set the component's render mode
locationComponent.setRenderMode(RenderMode.COMPASS);
initLocationEngine();
show hidden lines
enableLocationComponent(mapboxMap.getStyle());
show hidden lines

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

MainActivity.java
show hidden lines
// Variables needed to listen to location updates
private MainActivityLocationCallback callback = new MainActivityLocationCallback(this);
show hidden lines
private static class MainActivityLocationCallback
implements 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.
*/
@Override
public 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 coordinates
Toast.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 LocationComponent
if (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
*/
@Override
public 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 lines

OnSuccess() 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.

MainActivity.java
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 map
import 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 engine
import 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 component
import 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 implements
OnMapReadyCallback, PermissionsListener {
// Variables needed to initialize a map
private MapboxMap mapboxMap;
private MapView mapView;
// Variables needed to handle location permissions
private PermissionsManager permissionsManager;
// Variables needed to add the location engine
private 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 updates
private MainActivityLocationCallback callback = new MainActivityLocationCallback(this);
@Override
protected 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.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);
}
@Override
public void onMapReady(@NonNull final MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
mapboxMap.setStyle(Style.TRAFFIC_NIGHT,
new Style.OnStyleLoaded() {
@Override
public 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 request
if (PermissionsManager.areLocationPermissionsGranted(this)) {
// Get an instance of the component
LocationComponent locationComponent = mapboxMap.getLocationComponent();
// Set the LocationComponent activation options
LocationComponentActivationOptions locationComponentActivationOptions =
LocationComponentActivationOptions.builder(this, loadedMapStyle)
.useDefaultLocationEngine(false)
.build();
// Activate with the LocationComponentActivationOptions object
locationComponent.activateLocationComponent(locationComponentActivationOptions);
// Enable to make component visible
locationComponent.setLocationComponentEnabled(true);
// Set the component's camera mode
locationComponent.setCameraMode(CameraMode.TRACKING);
// Set the component's render mode
locationComponent.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);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onExplanationNeeded(List<String> permissionsToExplain) {
Toast.makeText(this, R.string.user_location_permission_explanation, Toast.LENGTH_LONG).show();
}
@Override
public 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 MainActivityLocationCallback
implements 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.
*/
@Override
public 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 coordinates
Toast.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 LocationComponent
if (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
*/
@Override
public 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();
}
}
}
@Override
protected void onStart() {
super.onStart();
mapView.onStart();
}
@Override
protected void onResume() {
super.onResume();
mapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
@Override
protected void onDestroy() {
super.onDestroy();
// Prevent leaks
if (locationEngine != null) {
locationEngine.removeLocationUpdates(callback);
}
mapView.onDestroy();
}
@Override
public 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.

Was this page helpful?