Mobile
Article

Add Elegant Maps to Your Android App with MapBox

By Valdio Veliu

Mapbox is open source SDK for pixel-perfect vector maps that promises the render speed and smoothness of a video game. If you are interested in integrating map functionality in your apps then Mapbox is a choice worth considering.

The Mapbox Android SDK overview and examples are a good starting point. In this tutorial I will create something more complex, an app that draws the best route from origin to destination on a map.

Final Project

Let’s get started!

You can find the code for the final project on GitHub.

To use Mapbox in your app you will need an API access token. To get one, create a Mapbox account and find yours here.

Mapbox Token

Add the token to strings.xml in your Android project:

<string name="accessToken">Your access token</string>

In AndroidManifest.xml add the following permissions for internet and location access.

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>

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

Note: In Android Marshmallow (API 23) and above it’s necessary to request permissions at runtime.

Installing Mapbox

Open build.gradle(Module: app) and add MapBox as a dependency.

repositories {
    mavenCentral()
}

dependencies {
    . . .

    compile('com.mapbox.mapboxsdk:mapbox-android-sdk:3.2.0@aar') {
        transitive = true
    }
    compile ('com.mapbox.mapboxsdk:mapbox-android-directions:1.0.0@aar'){
        transitive=true
    }

}

The first dependency represents Mapbox and the second is the Directions library to add driving, walking, and cycling routes to the app and draw them on the map.

The Map Layout

In the content_main.xml layout file replace the contents with the following:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:mapbox="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.valdio.mapboxintegration.MainActivity"
    tools:showIn="@layout/activity_main">

    <com.mapbox.mapboxsdk.views.MapView
        android:id="@+id/mapview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        mapbox:access_token="@string/accessToken" />

</RelativeLayout>

Initialize the variables you will need in the MainActivity class:

public class MainActivity extends AppCompatActivity {

  private MapView mapView = null;
  private String MAPBOX_ACCESS_TOKEN = "";
  private DirectionsRoute currentRoute = null;

  ...

In the onCreate() method of MainActivity.java delete the toolbar and floating action button code and initialize the map by adding:

String MAPBOX_ACCESS_TOKEN = getResources().getString(R.string.accessToken);

// Set up a standard Mapbox map
MapView mapView = (MapView) findViewById(R.id.mapview);
mapView.setAccessToken(MAPBOX_ACCESS_TOKEN);
mapView.setStyleUrl(Style.MAPBOX_STREETS); // specify the map style
mapView.setZoom(14); // zoom level
mapView.onCreate(savedInstanceState);

Mapbox maps need to implement the Activity’s lifecycle methods to avoid runtime errors, so add the necessary override functions.

@Override
protected void onStart() {
    super.onStart();
    mapView.onStart();
}

@Override
protected void onStop() {
    super.onStop();
    mapView.onStop();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    mapView.onDestroy();
}

@Override
protected void onResume() {
    super.onResume();
    mapView.onResume();
}

@Override
protected void onPause() {
    super.onPause();
    mapView.onPause();
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    mapView.onSaveInstanceState(outState);
}

Now that Mapbox is configured, it’s time to start building the app.

Adding a Marker to the Map

Add the following to the bottom of onCreate in MainActivity:

CameraPosition cameraPosition = new CameraPosition.Builder()
        .target(new LatLng(41.327752, 19.818666)) // Sets the center of the map to the specified location
        .zoom(13)                            // Sets the zoom level
        .build();

//set the user's viewpoint as specified in the cameraPosition object
mapView.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

//Add a marker to the map in the specified location
mapView.addMarker(new MarkerOptions()
        .position(new LatLng(41.327752, 19.818666))
        .title("MapBox Marker!")
        .snippet("Welcome to my marker."));

CameraPosition is a useful Mapbox class used to set position, angle, zoom and tilt of the user’s viewpoint.

This is how the marker looks on the map:

Marker Example

Get the Device Location.

For Mapbox to access the device location, location services must be enabled and the app should have permission to use them. As mentioned earlier, in Android Marshmallow (API 23) and later it’s necessary to request permissions at runtime.

Add the code to get the current location to a new function:

      private void myLocation() {

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return;
        }

        mapView.setMyLocationEnabled(true);
        mapView.setMyLocationTrackingMode(MyLocationTracking.TRACKING_FOLLOW);
        mapView.getMyLocation();
    }

And call this function after the map initialization code:

...
mapView.onCreate(savedInstanceState);

myLocation();

In this case tracking is set to TRACKING_FOLLOW, which means that the user’s location will be constantly monitored and the map will update as it changes.

Location Tracking

Almost There!

You now have a marker and location, next is drawing the route on the map.

This uses the second library imported in the dependencies, the ‘Directions library’, also created by the Mapbox team.

Here is how it works:

  1. Create two Waypoint locations, origin and destination.
  2. Create a MapboxDirections builder object, which is a network request to the Mapbox API containing the origin and destination Waypoints and the profile of the direction route (driving, walking or cycling).
  3. Execute the direction request asynchronously. The MapboxDirections class has a built in async request using the Retrofit API, using enqueue() to execute the request.
  4. The onResponse() method returns a Retrofit response, which is a standard JSON API response.
  5. The body of the response contains location coordinate pairs, the coordinates that will be drawn on the map.
  6. Using Mapbox Polyline the coordinates are drawn on the map. Polyline is a geometry feature with an unclosed list of coordinates drawn as a line.

To use the Direction library, get the device location as the origin waypoint and a location specified by the user by long clicking on the map as the destination waypoint.

Create Two Waypoint Locations and Add a Marker on the Destination.

Add the following to the bottom of the onCreate function:

mapView.setOnMapLongClickListener(new MapView.OnMapLongClickListener() {
    @Override
    public void onMapLongClick(LatLng point) {

        //Remove previously added markers
        //Marker is an annotation that shows an icon image at a geographical location
        //so all markers can be removed with the removeAllAnnotations() method.
        mapView.removeAllAnnotations();

        // Set the origin waypoint to the devices location
        Waypoint origin = new Waypoint(mapView.getMyLocation().getLongitude(), mapView.getMyLocation().getLatitude());

        // Set the destination waypoint to the location point long clicked by the user
        Waypoint destination = new Waypoint(point.getLongitude(), point.getLatitude());

        // Add marker to the destination waypoint
        mapView.addMarker(new MarkerOptions()
                .position(new LatLng(point))
                .title("Destination Marker")
                .snippet("My destination"));

        // Get route from API
        getRoute(origin, destination);

    }
});

Create a MapboxDirections Network Request and Run It Asynchronously.

Add the following method to get the best route:


private void getRoute(Waypoint origin, Waypoint destination) {
  MapboxDirections directions = new MapboxDirections.Builder()
          .setAccessToken(MAPBOX_ACCESS_TOKEN)
          .setOrigin(origin)
          .setDestination(destination)
          .setProfile(DirectionsCriteria.PROFILE_WALKING)
          .build();

  directions.enqueue(new Callback<DirectionsResponse>() {
      @Override
      public void onResponse(Response<DirectionsResponse> response, Retrofit retrofit) {

          // Display some info about the route
          currentRoute = response.body().getRoutes().get(0);
          showToastMessage(String.format("You are %d meters \nfrom your destination", currentRoute.getDistance()));

          // Draw the route on the map
          drawRoute(currentRoute);
      }

      @Override
      public void onFailure(Throwable t) {
          showToastMessage("Error: " + t.getMessage());
      }
  });
}

Draw the Polyline Based on the Coordinates on the Map.

Add this method to draw the route:


private void drawRoute(DirectionsRoute route) {
  // Convert List<Waypoint> into LatLng[]
  List<Waypoint> waypoints = route.getGeometry().getWaypoints();
  LatLng[] point = new LatLng[waypoints.size()];
  for (int i = 0; i < waypoints.size(); i++) {
      point[i] = new LatLng(
              waypoints.get(i).getLatitude(),
              waypoints.get(i).getLongitude());
  }

  // Draw Points on MapView
  mapView.addPolyline(new PolylineOptions()
          .add(point)
          .color(Color.parseColor("#38afea"))
          .width(5));
}

private void showToastMessage(String message) {
  Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}

Run the project, select two points and you should see something like the below.

Final App showing route

Going the Extra Mile

In this tutorial I covered the basics to get you started with the MapBox SDK and the Direction library. MapBox has more cool features for use in your app like different map styles, or for something different, custom vector maps.

Another library I recommend is the Geocoding library to turn coordinates into addresses or vice versa. To learn more about MapBox check out its mobile maps, were you can find information about the libraries mentioned in this tutorial.

More:

No Reader comments

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Mobile, once a week, for free.