Mapbox Tutorial For Android: Getting Started

In this tutorial, you’ll learn everything there is to setting up a simple GPS navigation app, using MapBox, by building an app called Where2Go!

Version

  • Kotlin 1.3, Android 4.1, Android Studio 3

Nowadays, many apps have a location function. You use them all the time, whether you’re ordering food delivery, booking a cab, or even doing laundry. Have you ever thought about building a location app of your own? You may think it’s too complicated or may take too much time to build something close to some of the impressive apps with location features that have been built for Android. Well not anymore! Thanks to the Mapbox SDK, you can build a cool Android app with location features. That’s exactly what you’ll do in this tutorial.

Using the Mapbox Android SDK, you’ll build an app using Mapbox Navigation called Where2Go. In this tutorial, you’ll learn how to:

  • Add the Mapbox library to project.
  • Show the user’s current location on the map.
  • Add a marker on the map and navigate turn by turn from the current location to the point where the marker is on the map.

If you want to learn a bit more about Mapbox, you can read more about it at the official website.

Prerequisites: This tutorial assumes that you understand the basics of Android development with Kotlin. If you’re new to Android development, please go through Beginning Android Development with Kotlin to understand the basics. If you’re new to Kotlin, check out this Introduction to Kotlin tutorial.

Getting Started

Before you learn about Mapbox, you have to download the starter and final projects by using the Download Materials link at the top or bottom of this tutorial. Launch Android Studio 3.3 or later, select the Open an existing Android Studio project option, then navigate to and select the starter project folder.

Open Project

Once Gradle build loading is complete, you can build and run the app to see what you have inside the starter project.

Starter Project Running

The first thing you’ll see after running the app is an empty screen and a floating action button. Not too moving. But in a bit, when you implement the navigation using Mapbox, you’ll be able to navigate anywhere you want! :]

Register For an Account with Mapbox

The first thing that you need to do before using Mapbox SDK is to register for an account. Then, once you successfully register, you’ll get an access token. This token is the key that you need to be able to use the Mapbox SDK.

Once you arrive at the Mapbox website, click on the Sign in button as pointed out by the red arrow.

Sign Up for Mapbox

You’ll be directed to another page which has a sign in form. You need to click on Sign up for Mapbox as shown in the red box.

Sign up for Mapbox

Now you need to fill out the sign up form and click Get started.

Mapbox Sign in form

Don’t worry, the registration is free. You can use Mapbox SDK to build a small application. Once your app gets popular, they’ll start to charge you. :]

Pay as You Go

Adding the Mapbox Dependency

First, open the build.gradle (Module:app) file and add compileOptions within android:

compileOptions {
  sourceCompatibility JavaVersion.VERSION_1_8
  targetCompatibility JavaVersion.VERSION_1_8
}

Second, add the Mapbox and Google Play Service Location library dependencies within dependencies:

implementation 'com.mapbox.mapboxsdk:mapbox-android-navigation-ui:0.26.0'
implementation ('com.mapbox.mapboxsdk:mapbox-android-sdk:6.8.1') {
  exclude group: 'group_name', module: 'module_name'
}
implementation 'com.google.android.gms:play-services-location:16.0.0'

Finally, open the build.gradle (Project) file, and add following lines inside the allprojects right under the jcenter().

mavenCentral()
maven { url 'https://mapbox.bintray.com/mapbox' }

Click Sync now to sync the project, which allows you to use the Mapbox library.

Sync Mapbox Android Project

Working with MapBox

In this section, you’ll learn how to get a MapBox access token, get the user’s current location, and show their location on the map.

Getting a Mapbox Access Token

Once you’ve successfully registered for an account with Mapbox, click your profile picture and choose Account.

Mapbox Choose Account

Inside the Account page is where you’ll create a brand new access token, or you can use the default public token, as you’ll see in this tutorial.

Configuring Mapbox to Show the Map

Open the activity_main.xml file. First, you need to add xmlns:mapbox inside RelativeLayout.

xmlns:mapbox="http://schemas.android.com/apk/res-auto"

Add that tag so you can access Mapbox attributes.

Next, add the following code to show the Mapbox map.

<com.mapbox.mapboxsdk.maps.MapView
  android:id="@+id/mapbox"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  mapbox:mapbox_styleUrl="@string/mapbox_style_mapbox_streets"/>

Mapbox has different StyleUrl attributes, which you can use to change the overall map appearance. The one that you’ll use is mapbox_style_mapbox_streets. This style is a plain styling of roads and transit networks.

Now open the AndroidManifest.xml file and add the following permissions.

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

Add the android.permission.ACCESS_FINE_LOCATION permission so that Mapbox SDK can detect your location and show it on the map. As for the android.permission.FOREGROUND_SERVICE permission, this is needed when you want to start the turn by turn navigation.

Next, open the MainActivity.kt file, add the following code before the call to the setContentView function:

Mapbox.getInstance(this, "Your Mapbox access token")

Bear in mind, you need to replace the string text with the actual Mapbox access token.

Since you’ll be using Kotlin Android Extensions, you can easily reference views by their id attributes defined in the XML. To initialize the mapbox view, add this code below the setContentView line:

mapbox.onCreate(savedInstanceState)

Handling the Mapbox lifecycle is really important, so add the following code below the onCreate function to complete the initialization and cleanup process:

@SuppressWarnings("MissingPermission")
override fun onStart() {
  super.onStart()
  mapbox.onStart()
}

override fun onResume() {
  super.onResume()
  mapbox.onResume()
}

override fun onPause() {
  super.onPause()
  mapbox.onPause()
}

override fun onStop() {
  super.onStop()
  mapbox.onStop()
}

override fun onDestroy() {
  super.onDestroy()
  mapbox.onDestroy()
}

override fun onLowMemory() {
  super.onLowMemory()
  mapbox.onLowMemory()
}

override fun onSaveInstanceState(outState: Bundle?) {
  super.onSaveInstanceState(outState)
    if (outState != null) {
      mapbox.onSaveInstanceState(outState)
    }
}

Mapbox has its own lifecycle methods for managing an Android openGL lifecycle. You must call those methods directly from the containing activity.

Note: You need to add @SuppressWarnings("MissingPermission") above onStart and the other methods, because these methods require you to implement permission handling. However, you don’t need that here because Mapbox handles it for you.

Build and run the app to see the result.

Mapbox Android App Running

Detect the User’s Current Location

Open the MainActivity.kt file and change the class declaration to make it implement the following interfaces.

class MainActivity : AppCompatActivity(), PermissionsListener, LocationEngineListener, OnMapReadyCallback

You need to use PermissionsListener to handle the location permission for devices running Android Marshmallow and later, while you need to use LocationEngineListener to locate the user’s location. Finally, you need OnMapReadyCallback, because this where you’ll include PermissionsListener and LocationEngineListener to begin locating where the user is and show that location on the map.

Android Studio will now complain with errors telling you to add the required imports. Hover your mouse over the error and choose Implement members.

Implement android member functions for Mapbox project

Make sure to select all the members and click OK.

Select all members

Android Studio will automatically include the following code inside MainActivity.kt.

//1
override fun onExplanationNeeded(permissionsToExplain: MutableList<String>?) {
        
}
//2
override fun onPermissionResult(granted: Boolean) {
        
}
//3
override fun onLocationChanged(location: Location?) {
        
}
//4
@SuppressWarnings("MissingPermission")
override fun onConnected() {
        
}
//5
override fun onMapReady(mapboxMap: MapboxMap?) {
        
}
//6
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {

}

Let’s go through this code together.

  1. onExplanationNeeded: Provide an explanation to the user for why you need the permission.
  2. onPermissionResult: Check whether the permission was granted or not by the user, and how the app will handle that action.
  3. onLocationChanged: Monitor user location changes.
  4. onConnected: This is where the app is fully connected and is able to receive location updates.
  5. onMapReady: The map is ready, and you can perform location related activities.
  6. onRequestPermissionsResult: Override this extra method, because it’s the one that handles all the permissions related work.

Now let’s add the required variables. You can add them at the top, before the onCreate function.

//1
val REQUEST_CHECK_SETTINGS = 1
var settingsClient: SettingsClient? = null

//2
lateinit var map: MapboxMap
lateinit var permissionManager: PermissionsManager
var originLocation: Location? = null

var locationEngine: LocationEngine? = null
var locationComponent: LocationComponent? = null

Let’s go through the code above and get to know those variables.

  1. You’ll use SettingsClient API to show an AlertDialog box where you’ll ask the user to turn on GPS to locate the user’s location.
  2. These variables are used for Mapbox, and are all related to getting the user’s location.

Next, you need to create 4 methods.

  1. enableLocation
  2. initializeLocationEngine
  3. setCameraPosition
  4. initializeLocationComponent
//1
fun enableLocation() {
        
}
//2
@SuppressWarnings("MissingPermission")
fun initializeLocationEngine() {
        

}

@SuppressWarnings("MissingPermission")
fun initializeLocationComponent() {
        
}

//3
fun setCameraPosition(location: Location) {

}

Let’s go through the code above.

  1. enableLocation: This is where you’ll enable location tracking to locate the user’s current location.
  2. initializeLocationEngine & initializeLocationComponent: These 2 functions are responsible for doing the actual work of locating the user’s location.
  3. setCameraPosition: This function handles zooming in on the user’s location in the map.

Add the following code inside the onCreate function.

mapbox.getMapAsync(this)

This is a callback which will be triggered when the Mapbox map is ready.

Next you need to initialize SettingsClient API.

settingsClient = LocationServices.getSettingsClient(this)

Now you need to add the following code inside onMapReady.

//1
map = mapboxMap ?: return
//2
val locationRequestBuilder = LocationSettingsRequest.Builder().addLocationRequest(LocationRequest()
    .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY))
//3
val locationRequest = locationRequestBuilder?.build()

settingsClient?.checkLocationSettings(locationRequest)?.run {
  addOnSuccessListener {
    enableLocation()
  }

  addOnFailureListener {
    val statusCode = (it as ApiException).statusCode

    if (statusCode == LocationSettingsStatusCodes.RESOLUTION_REQUIRED) {
      val resolvableException = it as? ResolvableApiException
      resolvableException?.startResolutionForResult(this@MainActivity, REQUEST_CHECK_SETTINGS)
    }
  }
 }

The code above is all related to SettingsClient API, so let’s go through it step by step.

  1. First, you initialize the map variable, because you’ll be using it later.
  2. Initialize locationRequestBuilder and pass the location request that the app will use.
  3. Finally, you use locationRequest to build location request. Then pass it to settingsClient and attach two types of listeners:
    • addOnSuccessListener: When the request is successful, you’ll call enableLocation to initiate location tracking.
    • addOnFailureListener: When the request fails, check to see the reason why by looking at the exception status code. If the exception is LocationSettingsStatusCodes.RESOLUTION_REQUIRED, then the app should handle that exception by calling startResolutionForResult.

Next, you need to handle the exception result by overriding onActivityResult. Add the following code below onCreate.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
  super.onActivityResult(requestCode, resultCode, data)

  if (requestCode == REQUEST_CHECK_SETTINGS) {
    if (resultCode == Activity.RESULT_OK) {
      enableLocation()
    } else if (resultCode == Activity.RESULT_CANCELED) {
      finish()
    }
  }
}

First, check if the requestCode is correct and if resultCode is equal to Activity.RESULT_OK. Then, you call the enableLocation method to initiate location tracking.

If resultCode is equal to Activity.RESULT_CANCELED, the user has cancelled the request and you call finish() to close the whole app.

The reason you call finish() is because the app needs the user to turn ON GPS. If the user fails to do so, the app won’t work, so it’s best to close the app.

Now it’s time to see some progress, so build and run the app. :]

mapbox android project prompting for GPS permission

If GPS is not turned ON in your device, the app will prompt you with the AlertDialog telling you to turn it ON. Once you tap on OK, the device GPS will turn ON. Otherwise if you tap on the NO, THANKS button, the app will close.

If GPS in your device is ON, the app will not prompt you with AlertDialog.

Displaying the Location on Map

Now get the user’s current location and show it on the map. First, implement the enableLocation function by adding the following code inside it.

if (PermissionsManager.areLocationPermissionsGranted(this)) {
  initializeLocationComponent()
  initializeLocationEngine()
} else {
  permissionManager = PermissionsManager(this)
  permissionManager.requestLocationPermissions(this)
}

Check to see if the location permission was granted. If it was, call initializeLocationComponent and initializeLocationEngine to start locating the user. Otherwise, the app will ask the user for permission to access the location.

Next, modify the initializeLocationEngine function by adding the following code.

locationEngine = LocationEngineProvider(this).obtainBestLocationEngineAvailable()
locationEngine?.priority = LocationEnginePriority.HIGH_ACCURACY
locationEngine?.activate()
locationEngine?.addLocationEngineListener(this)

val lastLocation = locationEngine?.lastLocation
if (lastLocation != null) {
  originLocation = lastLocation
  setCameraPosition(lastLocation)
} else {
  locationEngine?.addLocationEngineListener(this)
}

In this code:

  • First, you get the location.
  • Next, set the priority as high.
  • Then, attach the onLocationChanged & onConnected listeners so that the app can respond to any changes.
  • Finally, try to get the user’s last location and pass it to setCameraPosition method. If the user doesn’t have a last location, call the location listeners again.

Now you need to include the following code inside initializeLocationComponent.

locationComponent = map.locationComponent
locationComponent?.activateLocationComponent(this)
locationComponent?.isLocationComponentEnabled = true
locationComponent?.cameraMode = CameraMode.TRACKING

In this code, you:

  • Initialize the locationComponent.
  • Activate and enable it to start listening for the user’s location.
  • Set cameraMode to CameraMode.TRACKING.

Modify setCameraPosition to enable zooming into the user’s location.

map.animateCamera(CameraUpdateFactory.newLatLngZoom(LatLng(location.latitude,
location.longitude), 30.0))

Here, you call animateCamera to move the camera map to that exact position based on location.latitude and location.longitude values, then you set the zoom value to 30.0.

Next, set up onExplanationNeeded, onPermissionResult , onLocationChanged and onConnected.

Add the following code inside onExplanationNeeded.

Toast.makeText(this, "This app needs location permission to be able to show your location on the map", Toast.LENGTH_LONG).show()

The app will now show a Toast message while asking the user to give permission to access the location.

Add the following code inside onPermissionResult.

if (granted) {
   enableLocation()
} else {
  Toast.makeText(this, "User location was not granted", Toast.LENGTH_LONG).show()
  finish()
}

Check if the location permission was granted, then initiate location tracking. If the permission wasn’t granted, the app will show a Toast message and close the app.

Next, add the following code inside onLocationChanged.

location?.run {
  originLocation = this
  setCameraPosition(this)
}

Pass the user’s latest location to the setCameraPosition method so the map will show the current user’s location all the time.

Add the following code inside onConnected.

locationEngine?.requestLocationUpdates()

This will call locationEngine to track the user’s location.

Now you need to add this code inside onRequestPermissionsResult.

permissionManager.onRequestPermissionsResult(requestCode, permissions, grantResults)

permissionManager handles all the permission related work.

Now you need to modify a few more methods before you can run the app. First you need to include these lines of code inside onStart before mapbox.onStart().

if (PermissionsManager.areLocationPermissionsGranted(this)) {
  locationEngine?.requestLocationUpdates()
  locationComponent?.onStart()
}

Now, the app will retrieve the user’s location only if the location permission was granted by the user.

Next, you need to modify onStop.

locationEngine?.removeLocationUpdates()
locationComponent?.onStop()

The app will stop retrieving the user’s location updates when the onStop method is called.

Finally, you need to add the following line in the onDestroy method.

locationEngine?.deactivate()

This means that the app will disconnect from locationEngine and will no longer receive any location updates after the onDestroy() method is called. For information on MVP and lifecycle, see this tutorial.

Now it’s time to build and run the app to see the result.

mapbox android project running with map loaded

Customizing the Map Appearance

Open the MainActivity.kt file, and modify the class header by adding the MapboxMap.OnMapClickListener interface.

Now Android Studio will complain, telling you to implement the required method. When you choose to implement the method, make sure you select onMapClick and click OK.

After you click the OK button, Android Studio will automatically add this new override method.

override fun onMapClick(point: LatLng) {
        
}

First you need to modify the enableLocation function by adding the following code.

map.addOnMapClickListener(this)

You need to add this because you want the map to respond to user taps only when the user’s location is visible on the map, and because this class implements the MapboxMap.OnMapClickListener interface.

Modify the onMapClick method by adding the following code.

map.addMarker(MarkerOptions().position(point))

Here you add a marker at a particular position by passing the point variable.

Now build and run the app to see the marker. :]

mapbox android project with multiple android markers

It’s cool to finally see a marker on the map, but it doesn’t look right when adding multiple markers all over the map, right?

You can fix this by adding the following code inside the onMapClick method.

if(!map.markers.isEmpty()){
  map.clear()
}

This will remove any marker before adding a new one on the map.

Build and run the app to see the result.

mapbox android project with one map marker

You can further customize the marker by adding various cool things to it.

map.addMarker(MarkerOptions().setTitle("I'm a marker :]").position(point))

Here, you add a string title that appears on the marker. Go ahead and run the app to see it for yourself.

You can also add a snippet to the marker.

map.addMarker(MarkerOptions().setTitle("I'm a marker :]").setSnippet("This is a snippet about this marker that will show up here").position(point))

Now build and run the app to see the snippet.

Using the Mapbox Navigation API

In this section, you’ll learn how to use the Mapbox navigation api to add turn by turn navigation inside the app.

Direct the User Between Locations

Open the MainActivity.kt file, and add the following code at the top, before onCreate.

var navigationMapRoute: NavigationMapRoute? = null
var currentRoute: DirectionsRoute? = null

Here, you declare the required variables, which you’ll use later for navigation.

Next, you need to create a function called getRoute that takes two arguments, originPoint and endPoint. Make sure to import the org.mapbox.Point class, instead of the default Android implementation.

fun getRoute(originPoint: Point, endPoint: Point) {

}

You will use this function later for user navigation.

Now, you need to modify the onMapClick function by adding the following code.

checkLocation()
originLocation?.run {
  val startPoint = Point.fromLngLat(longitude, latitude)
  val endPoint = Point.fromLngLat(point.longitude, point.latitude)

  getRoute(startPoint, endPoint)
}

And create the checkLocation function, which will try to set the originLocation field to the last known location, if there isn’t any location present.

@SuppressLint("MissingPermission")
private fun checkLocation() {
  if (originLocation == null) {
    map.locationComponent.lastKnownLocation?.run {
      originLocation = this
    }
  }
}

In this code, you initialize startPoint by passing originLocation the longitude and latitude, while you initialize endPoint by passing point the same.

Finally, you’ll call getRoute by passing these two parameters: startPoint and endPoint.

At the moment, the getRoute function doesn’t do anything. You can make it do something by adding the following code.

Make sure to import the retrofit2 classes when importing Callback, Call and Response types.

NavigationRoute.builder(this) //1
  .accessToken(Mapbox.getAccessToken()!!) //2
  .origin(originPoint) //3
  .destination(endPoint) //4
  .build() //5
  .getRoute(object : Callback<DirectionsResponse> { //6
    override fun onFailure(call: Call<DirectionsResponse>, t: Throwable) {

    }

    override fun onResponse(call: Call<DirectionsResponse>,
      response: Response<DirectionsResponse>) {

    }
  })

Let’s go through that code block together.

  1. NavigationRoute.builder: Start the navigation by first passing the current Context.
  2. accessToken: This gets Mapbox an access token
  3. origin: Set the start point for this navigation
  4. destination: Set the ending/destination point for this navigation
  5. build: Call this to build up the navigation
  6. getRoute: Call this when you want to handle success and failure cases

Now you want to handle the onFailure case by adding the following code.

Log.d("MainActivity", t.localizedMessage)

Here the app will print a message in the Logcat when a failure case happens.

Finally, you need to handle the onResponse case by adding the code below.

if (navigationMapRoute != null) {
  navigationMapRoute?.updateRouteVisibilityTo(false)
} else {
  navigationMapRoute = NavigationMapRoute(null, mapbox, map)
}
       
currentRoute = response.body()?.routes()?.first()
if (currentRoute != null) {
  navigationMapRoute?.addRoute(currentRoute)
}

The navigationMapRoute field is responsible for drawing a line on the map, starting from current location to the destination.

You first check if navigationMapRoute is empty or not. If it’s not empty, you need to remove the route. Otherwise, initialize navigationMapRoute by passing three parameters:

  1. MapboxNavigation: This is a navigation instance of MapboxNavigation that you pass in case if you want to reroute during the navigation session. For this example you would pass a null.
  2. mapbox: This is the map that you want to draw the route on top of.
  3. MapboxMap: This will apply the route.

Initialize currentRoute by accessing the response body and getting the first route from the routes() list.

Finally, before you add any route, check whether there is already a route and then add that route to navigationMapRoute.

Now build and run the app to see the user’s route on the map! :]

It’s cool to see that blue line on the map showing the user route, right? But it’ll be really cool if you can actually start the navigation from a starting point until you reach the destination. Continue reading to find out how. :]

Inside btnNavigate, the setOnClickListener method is where you will add the following code.

val navigationLauncherOptions = NavigationLauncherOptions.builder() //1
  .directionsRoute(currentRoute) //2
  .shouldSimulateRoute(true) //3
  .build()

NavigationLauncher.startNavigation(this, navigationLauncherOptions) //4

Let’s go through that code.

  1. You create a constant navigationLauncherOptions, which you then initialize with NavigationLauncherOptions. NavigationLauncherOptions is part of Mapbox’s classes, which allow you to build the navigation route.
  2. directionsRoute: This is the actual route that you initialized earlier.
  3. shouldSimulateRoute: This is used to simulate the actual turn by turn navigation. You can enable or disable this as you like, and then call build().
  4. NavigationLauncher: This is a class that you can use to start the navigation by passing two parameters: Context and navigationLauncherOptions.

Now build and run the app.

mapbox android app with turn by turn navigation

It feels awesome to finally see the navigation working, but what will happen when you tap on the navigation button without first adding any marker on the map? The result is that the app will crash.

Here is how you can fix it. Open the MainActivity.kt file and add the following code to OnCreate.

btnNavigate.isEnabled = false

Here you disable the navigation button first in order to avoid starting the navigation before adding any marker on the map.

Now you can enable it inside the getRoute method, at the end of the onResponse block.

btnNavigate.isEnabled = true

You can now build and run the app. You can tap on the navigation button only when there’s a marker visible on the map. Otherwise, the button will be disabled.

Where to Go from Here?

You can download the completed project files by clicking on the Download Materials button at the top or bottom of this tutorial.

You can do even more with Mapbox Navigation API, for example:

  1. Changing the language when you start turn by turn navigation.
  2. Changing the colors of the navigation UI.
  3. Setting up listeners to know whether the user is still in route or has reached the destination.

To learn more about Mapbox Navigation API, take a look at the documentation.

We hope you enjoyed this tutorial, and if you have any questions or comments, please join the forum discussion below!

Add a rating for this content

Contributors

Comments