Migrate to v2

Mapbox Navigation SDK for Android v2 is a major new version of the SDK. To install the latest version of the SDK, follow the installation instructions and use this guide to update your application from v1 to v2.

Requirements

Like in v1, the Mapbox Navigation SDK for Android v2 is compatible with applications that:

Artifacts

v1

In v1 there were two artifacts to choose from:

  • com.mapbox.navigation:core:{version}: To use the Navigation SDK without pre-built UI components.
  • com.mapbox.navigation:ui:{version}: To use the Navigation SDK with pre-built UI components, which also includes navigation:core as a dependency.

v2

In Navigation SDK v2, all features are available under one Maven artifact: com.mapbox.navigation:android:{version}.

In addition to the one grouping artifact, there are also multiple granular ones that you can pick-and-choose if you’re not planning to use all of the features and want to reduce the binary size of your application:

  • com.mapbox.navigation:core offers all the core localization features from v1.
  • com.mapbox.navigation:ui-maps offers the route line and arrow APIs, navigation camera, building highlight and extrusion, extended visual guidance, and other tools and features that integrate with the Mapbox Maps SDK.
  • com.mapbox.navigation:ui-maneuver offers the maneuver view and its APIs that replace v1's InstructionView.
  • com.mapbox.navigation:ui-tripprogress offers the trip progress view and APIs that replace v1's SummaryBottomSheet.
  • com.mapbox.navigation:ui-voice offers all necessary APIs to play voice instructions.
  • com.mapbox.navigation:ui-speedlimit offers the speed limit view and its APIs that replace v1's SpeedLimitView.
guide
Modularization

Read more about leveraging the Navigation SDK v2's modular architecture.

Dependencies

Maps SDK

In v2, the Navigation SDK upgrades to Mapbox Maps SDK v10. Maps SDK v10 offers 3D maps and improved performance.

Maps SDK v10 is also a SEMVER major release with breaking API changes. If the application you are migrating uses a map, make sure you read the Maps SDK migration guide before reading the navigation-specific content below.

guide
Migrate to v10

Upgrade your application from the Mapbox Maps SDK for Android v9 to v10.

Kotlin

The Navigation SDK v2 is written 100% in Kotlin, the official language recommended by Google for Android development. This is a change from how the pre-v2 versions were a mix of Java and Kotlin.

Java support

While the Mapbox Navigation SDK for Android is built with Kotlin, Kotlin is 100% interoperable with Java. Applications with a Java codebase can interact properly with the public APIs exposed by Mapbox SDKs for Android.

For information on Java and Kotlin interoperability see the offical Kotlin docs:

If you experience any issues using the Mapbox Navigation SDK for Android with Java, contact us.

Core components in v2

Core localization components in the Navigation SDK v2 are mostly unchanged architecturally, but introduce a couple breaking changes that improve the experience or clarify the behavior of the SDK to streamline the integration.

The following symbols were removed, renamed, or changed:

Request a route

MapboxNavigation#requestRoutes no longer automatically sets the result as the primary route for the navigation experience. This means that you should call MapboxNavigation#setRoutes with a result when calling MapboxNavigation#requestRoutes.

This change allows for dispatching and managing multiple route requests concurrently, including canceling with MapboxNavigation#cancelRouteRequest.

v1

mapboxNavigation?.requestRoutes(
    RouteOptions.builder().applyDefaultParams()
        .accessToken(Utils.getMapboxAccessToken(applicationContext))
        .coordinates(originLocation.toPoint(), null, latLng.toPoint())
        .profile(DirectionsCriteria.PROFILE_DRIVING_TRAFFIC)
        .build(),
    routesReqCallback
)

private val routesReqCallback = object : RoutesRequestCallback {
    override fun onRoutesReady(routes: List<DirectionsRoute>) {
        if (routes.isNotEmpty()) {
            directionRoute = routes[0]
            navigationMapboxMap?.drawRoute(routes[0])
            startNavigation.visibility = View.VISIBLE
        } else {
            startNavigation.visibility = View.GONE
        }
    }

    override fun onRoutesRequestFailure(throwable: Throwable, routeOptions: RouteOptions) {
        // Handle error
    }

    override fun onRoutesRequestCanceled(routeOptions: RouteOptions) {
        // Handle cancelation
    }
}

v2

val routeOptions = RouteOptions.builder()
    .applyDefaultNavigationOptions()
    .applyLanguageAndVoiceUnitOptions(this)
    .coordinates(
        origin = origin,
        destination = destination
    )
    .layersList(listOf(mapboxNavigation.getZLevel(), null))
    .build()
mapboxNavigation.requestRoutes(
    routeOptions,
    object : RouterCallback {
        override fun onRoutesReady(
            routes: List<DirectionsRoute>,
            routerOrigin: RouterOrigin
        ) {
            mapboxNavigation.setRoutes(routes)
        }

        override fun onFailure(
            reasons: List<RouterFailure>,
            routeOptions: RouteOptions
        ) {
            // no impl
        }

        override fun onCanceled(routeOptions: RouteOptions, routerOrigin: RouterOrigin) {
            // no impl
        }
    }
)
guide
Route generation

Read more about requesting routes using the Navigation SDK v2.

MapboxNavigation#defaultNavigationOptionsBuilder has been removed. In v2, the NavigationOptions class now contains all the right defaults out of the box.

The OnboardRouterOptions class was renamed to RoutingTilesOptions to better reflect the purpose. If you are using a custom base URI, dataset, or version of the tiles, those are now separate fields in the builder rather than a single URI path.

Predictive caching

The default directory for caching navigation tiles changed:

  • v1: APP_FOLDER/Offline/api.mapbox.com/$tilesVersion/tiles
  • v2: APP_FOLDER/mbx_nav/tiles/api.mapbox.com

Caches from previous versions of the Navigation SDK are not compatible with v2.

When migrating make sure you have cleaned up the old navigation tiles cache folder to reclaim disk space:

  • Delete any folders used for caching with previous versions of the SDK including the default one.
  • OnboardRouterOptions enables you to specify a path where navigation tiles will be saved. If a custom directory was used, you should remove it, too.
    • Be sure to configure the custom path before starting a free drive or an active guidance session if you don't want to use the default location.
guide
Offline: Predictive caching

Read more about how predictive caching works in the Navigation SDK v2.

UI components in v2

The general approach to UI components changed in v2. It exposes direct access to the data that powers pre-built UI components (Views) so you can either render that data in a Mapbox-designed UI component or a custom one. Read more in the Approach to UI components section below.

The following v1 symbols were removed in v2:

The following v1 symbols were replaced in v2:

Approach to UI components

Unlike in the Navigation SDK v1, where the views were exposed with no access to data, the v2 UI components now focus on separating the data transformation logic (preparing for presentation) from the actual presentation layer that updates the app’s UI elements. You can now access APIs specifically catered to give you access to the data, prepared using raw data points from core modules. Based on the data retrieved from these APIs, you can choose to render it either on a Mapbox-designed View or your on custom view.

This separation requires additional synchronization on the app level, but gives you greater control over the user experience as your app scales. In v2 you now have control over the UI components' lifecycle to better fit them into more complex setups. This is also true for UI components that interact directly with the Mapbox Map. The presentation data is prepared separately from the Map itself, and you can dispatch updates at the correct time, depending on the Map’s placement in the view hierarchy and its lifecycle.

The NavigationView class, which provided a complete drop-in UI for turn-by-turn navigation, was removed in v2.

In v2 there are more opportunities for customization. You can now build your own UI for turn-by-turn navigation using our pre-built UI components, your own custom UI components, or some combination of the two.

v1

XML layout:

<com.mapbox.navigation.ui.NavigationView
    android:id="@+id/navigationView"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintBottom_toBottomOf="parent" />

Activity:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    navigationView.onCreate(savedInstanceState)
    navigationView.initialize(this)
}

v2

In v2, you will need to add specific views to your layout file individually and pass data to those components manually. See the Turn-by-turn navigation: User interface guide and the Add a complete turn-by-turn experience example to see all the available Mapbox-designed UI components and how to fit them together.

guide
Turn-by-turn navigation: User interface

Build a user interface for a turn-by-turn navigation experience using the Mapbox Navigation SDK for Android's pre-built UI components.

example
Add a complete turn-by-turn experience

Render a complete turn-by-turn experience using all relevant Navigation SDK APIs and pre-built UI components.

InstructionView was replaced

In v2, the maneuvers interface replaces v1's InstructionView.

The maneuvers interface has several components that are responsible for updating the current maneuver instructions with banner-related data. MapboxManeuverView allows you to quickly integrate the maneuver instructions in your app by passing it data from MapboxManeuverApi. But, you can also access the component parts of MapboxManeuverView individually to create a custom experience.

You can also instantiate and invoke MapboxManeuverApi independent of MapboxManeuverView to retrieve the data and render it on a custom maneuver view.

v1

Drop-in UI: This UI component was included in v1's pre-built UI (NavigationView). In v2 you will have to add this UI component manually.

InstructionView component: If you used the InstructionView directly in v1, your implementation likely looks something like this:

XML layout:

<com.mapbox.navigation.ui.instruction.InstructionView
    android:id="@+id/instructionView"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:gravity="top"
    android:visibility="invisible"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    tools:visibility="visible" />

Activity:

// Update the instruction view when route progress changes.
private val routeProgressObserver = object : RouteProgressObserver {
    override fun onRouteProgressChanged(routeProgress: RouteProgress) {
        instructionView.updateDistanceWith(routeProgress)
    }
}
// Update the instruction view when banner instructions change.
private val bannerInstructionObserver = object : BannerInstructionsObserver {
    override fun onNewBannerInstructions(bannerInstructions: BannerInstructions) {
        instructionView.updateBannerInstructionsWith(bannerInstructions)
    }
}

v2

To replace InstructionView, add MapboxManeuverView to your layout and update data using MapboxManeuverApi:

XML layout:

<com.mapbox.navigation.ui.maneuver.view.MapboxManeuverView
    android:id="@+id/maneuverView"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:visibility="gone"
    android:layout_marginStart="8dp"
    android:layout_marginEnd="8dp"
    android:layout_marginTop="8dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintStart_toStartOf="parent" />

Activity:

// Define distance formatter options
val distanceFormatterOptions = mapboxNavigation.navigationOptions.distanceFormatterOptions

// Create an instance of the MapboxManeuverApi
private val maneuverApi = MapboxManeuverApi(
    MapboxDistanceFormatter(distanceFormatterOptions)
)

// Get a list of maneuvers every time route progress changes and
// display the updated maneuver data the ManeuverView
private val routeProgressObserver = RouteProgressObserver { progress ->
    val maneuvers = maneuverApi.getManeuvers(progress)
    maneuvers.fold(
        { error -> 
            // handle error
        },
        {
            maneuverView.renderManeuvers(maneuvers)
        }
    )
}

For more details on adding the maneuvers UI component to your application, follow the instructions in the Maneuver instructions guide.

guide
Maneuver instructions

Read more about how maneuver instructions work in the Navigation SDK v2.

example
Render maneuver instructions for a route

Draw maneuver instructions using the Maneuver API and MapboxManeuverView.

SummaryBottomSheet was replaced

In v2, the trip progress interface replaces v1's SummaryBottomSheet.

The trip progress interface has several components that are responsible for updating trip related data on to the screen. MapboxTripProgressView allows you to quickly display trip-related data in your app by passing it data from MapboxTripProgressApi.

For more customized requirements you can use the MapboxTripProgressApi to retrieve the data and render it on your custom trip progress view.

v1

Drop-in UI: This UI component was included in v1's pre-built UI (NavigationView). In v2 you will have to add this UI component manually.

SummaryBottomSheet component: If you used the SummaryBottomSheet directly in v1, your implementation likely looks something like this:

XML layout:

<com.mapbox.navigation.ui.summary.SummaryBottomSheet
    android:id="@+id/summaryBottomSheet"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:visibility="gone"
    app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />

Activity:

private val routeProgressObserver = object : RouteProgressObserver {
    override fun onRouteProgressChanged(routeProgress: RouteProgress) {
        summaryBottomSheet.update(routeProgress)
    }
}

v2

To replace SummaryBottomSheet, add MapboxTripProgressView to your layout and update data using MapboxTripProgressApi:

XML layout:

<com.mapbox.navigation.ui.tripprogress.view.MapboxTripProgressView
    android:id="@+id/tripProgressView"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:visibility="gone"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintBottom_toBottomOf="parent" />

Activity:

// Get notified with progress along the currently active route.
private val routeProgressObserver = RouteProgressObserver { progress ->
    val tripProgress = tripProgressApi.getTripProgress(progress)
    binding.tripProgressView.render(tripProgress)
}

For more details on adding the trip progress UI component to your application, follow the instructions in the Trip progress guide.

guide
Trip progress

Read more about how the trip progress component works in the Navigation SDK v2.

example
Render trip progress information

Draw trip progress information using the Trip Progress API and MapboxTripProgressView.

In v1, the NavigationMapRoute class included both a route line and route arrow indicating the next maneuver. In v2, you can interact with the route line and route arrow interfaces directly and independently. The MapRouteLineInitializedCallback is not available in v2.

In v2, MapboxRouteLineView allows you to quickly integrate a route line into your app by passing it data from MapboxRouteLineApi and rendering it on the MapView. You can also instantiate and invoke MapboxRouteLineApi independent of MapboxRouteLineView to retrieve relevant data and render a custom route line.

In v2, MapboxRouteArrowView allows you to quickly integrate a route arrow into your app by passing it data from MapboxRouteArrowApi and rendering it on the MapView. You can also instantiate and invoke MapboxRouteArrowApi independent of MapboxRouteArrowView to retrieve relevant data and render a custom arrow.

v1

Drop-in UI: These UI components were included in v1's pre-built UI (NavigationView). In v2 you will have to add these UI components manually.

NavigationMapRoute class: If you used the NavigationMapRoute class directly in v1, your implementation likely looks something like this:

XML layout:

<com.mapbox.mapboxsdk.maps.MapView
    android:id="@+id/mapView"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintBottom_toBottomOf="parent" />

Activity:

navigationMapRoute = NavigationMapRoute.Builder(mapView, mapboxMap, this)
    .withVanishRouteLineEnabled(true)
    .withMapboxNavigation(mapboxNavigation)
    .build()
fun onMapReady(mapboxMap: MapboxMap) {
  if (activeRoute != null) {
      final List<DirectionsRoute> routes = listOf(activeRoute)
      navigationMapRoute.addRoutes(routes)
  }
}

private val routesReqCallback = object : RoutesRequestCallback {
    override fun onRoutesReady(routes: List<DirectionsRoute>) {
      if (!routes.isEmpty()) {
        activeRoute = routes.get[0]
        navigationMapRoute.addRoutes(routes)
      }
    }
}

v2

To replace NavigationMapRoute, add MapboxRouteLineView to your layout and update data using MapboxRouteLineApi:

XML layout:

<com.mapbox.maps.MapView
    android:id="@+id/mapView"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintBottom_toBottomOf="parent" />

Activity:

// Initialize route line view to draw arrows on the map
val mapboxRouteLineOptions = MapboxRouteLineOptions.Builder(this)
    .withRouteLineBelowLayerId("road-label")
    .build()
routeLineApi = MapboxRouteLineApi(mapboxRouteLineOptions)
routeLineView = MapboxRouteLineView(mapboxRouteLineOptions)

// Initialize maneuver arrow view to draw arrows on the map
val routeArrowOptions = RouteArrowOptions.Builder(this)
    .withAboveLayerId(TOP_LEVEL_ROUTE_LINE_LAYER_ID)
    .build()
}
routeArrowApi = MapboxRouteArrowApi()
routeArrowView = MapboxRouteArrowView(routeArrowOptions)

// Whenever route progress is updated, generate the route arrow to
// represent the next maneuver, update data, and pass it to the view
// class to visualize the updates on the map.
private val routeProgressObserver = RouteProgressObserver { routeProgress ->
    val arrowUpdate = routeArrowApi.addUpcomingManeuverArrow(routeProgress)
    mapboxMap.getStyle()?.apply {
        // Render the result to update the map.
        routeArrowView.renderManeuverUpdate(this, arrowUpdate)
    }
}

// Whenever the route data changes, draw the route line on the map.
private val routesObserver: RoutesObserver = RoutesObserver { routeUpdateResult ->
    // Wrap the DirectionRoute objects and pass them to the MapboxRouteLineApi
    // to generate the data necessary to draw the route(s) on the map.
    val routeLines = routeUpdateResult.routes.map { RouteLine(it, null) }
    routeLineApi.setRoutes(
        routeLines
    ) { value ->
        // The MapboxRouteLineView expects a non-null reference to
        // the map style. The data generated by the call to the
        // MapboxRouteLineApi above must be rendered by the
        // MapboxRouteLineView in order to visualize the changes
        // on the map.
        mapboxMap.getStyle()?.apply {
            routeLineView.renderRouteDrawData(this, value)
        }
    }
}
Removed MapRouteLineInitializedCallback

The MapRouteLineInitializedCallback is not available in v2.

As soon as you call MapboxRouteLineView#render, the layers are added to the map synchronously, so this is the point in time after which you can reference them for z-positioning other runtime layers. You can check RouteLayerConstants for layer IDs. Also, you may call MapboxRouteLineView#initializeLayers in advance to initialize the layers manually.

For more details on adding the route line and route arrow UI components to your application including alternative routes and vanishing route lines, follow the instructions in the Route line and Route arrow guides.

guide
Route line

Read more about how route lines work in the Navigation SDK v2.

guide
Route arrow

Read more about how the new route arrow component works in the Navigation SDK v2.

example
Draw route lines on a map

Draw route lines on a map using the route line and route arrow APIs and the related MapboxRouteLineView and MapboxRouteArrowView.

Building*HighlightLayer was replaced

In v1, there were two separate classes, BuildingExtrusionHighlightLayer and BuildingFootprintHighlightLayer, for highlighting a specific building footprint Polygon or extruding a footprint as a 3D building shape. In v2, the functionality associated with those classes is now handled by a single buildings interface.

In v2, MapboxBuildingView allows you to quickly integrate building-related data in your app by passing it data from MapboxBuildingsApi and rendering it on the MapView.

You can also instantiate and invoke MapboxBuildingsApi independent of MapboxBuildingView to retrieve the data and render it on your custom building view.

v1

Drop-in UI: Building highlighting was included in v1's pre-built UI (NavigationView). In v2 you will have to add these UI components manually.

BuildingExtrusionHighlightLayer or BuildingFootprintHighlightLayer class: If you used the BuildingExtrusionHighlightLayer or BuildingFootprintHighlightLayer class directly in v1, your implementation likely looks something like this:

XML layout:

<com.mapbox.mapboxsdk.maps.MapView
    android:id="@+id/mapView"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintBottom_toBottomOf="parent" />

Activity:

override fun onRouteProgressChanged(routeProgress: RouteProgress) {
    // Show the building extrusion when the device is a certain
    // number of meters away from the end of the DirectionsRoute.
    if (routeProgress.distanceRemaining < 250f) {
        mapboxMap.getStyle {
            // Initialize the Nav UI SDK's BuildingExtrusionHighlightLayer class.
            val buildingExtrusionHighlightLayer = BuildingExtrusionHighlightLayer(mapboxMap)

            buildingExtrusionHighlightLayer.queryLatLng = highlightQueryLatLng

            buildingExtrusionHighlightLayer.updateVisibility(true)
        }
    }
}

v2

To replace BuildingExtrusionHighlightLayer or BuildingFootprintHighlightLayer, add MapView to your layout and update data using MapboxBuildingsApi:

XML layout:

<com.mapbox.maps.MapView
    android:id="@+id/mapView"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintBottom_toBottomOf="parent" />

Activity:

val buildingView = MapboxBuildingView()
val buildingApi = MapboxBuildingsApi(mapboxMap)

// The callback contains a list of buildings returned as
// a result of querying the MapboxMap. If no buildings
// are available, the list is empty.
private val callback = MapboxNavigationConsumer<Expected<BuildingError, BuildingValue>> { expected ->
    expected.fold(
        {
          // Handle error
        },
        { value ->
            mapboxMap.getStyle { style ->
                buildingView.highlightBuilding(style, value.buildings)
            }
        }
    )
}

private val arrivalObserver: ArrivalObserver = object : ArrivalObserver {
    override fun onFinalDestinationArrival(routeProgress: RouteProgress) {
        buildingApi.queryBuildingOnFinalDestination(routeProgress, callback)
    }

    override fun onNextRouteLegStart(routeLegProgress: RouteLegProgress) {
        mapboxMap.getStyle { style ->
            buildingView.removeBuildingHighlight(style)
        }
    }

    override fun onWaypointArrival(routeProgress: RouteProgress) {
        buildingApi.queryBuildingOnWaypoint(routeProgress, callback)
    }
}

For more details on adding highlighted buildings to your application, follow the instructions in the Building highlights guide.

guide
Building highlights

Read more about how building highlights work in the Navigation SDK v2.

example
Render building extrusion on arrival

Use the example to render building extrusions on arrival using building API and MapboxBuildingView.

SpeedLimitView was replaced

In v2, the speed limit interface replaces v1's SpeedLimitView.

The speed limit interface includes several components that are responsible for rendering and updating speed limit information on to the screen. MapboxSpeedLimitView allows you to quickly display speed limit information in your app by passing it data from MapboxSpeedLimitApi.

You can also instantiate and invoke MapboxSpeedLimitApi independent of MapboxSpeedLimitView to retrieve relevant data and render a custom speed limit component.

v1

Drop-in UI: This UI component was included in v1's pre-built UI (NavigationView). In v2 you will have to add this UI component manually.

SpeedLimitView component: If you used the SpeedLimitView class directly in v1, your implementation likely looks something like this:

XML layout:

<com.mapbox.navigation.ui.speed.SpeedLimitView
    android:id="@+id/speedLimitView"
    android:layout_width="60dp"
    android:layout_height="60dp"
    android:layout_marginTop="16dp"
    android:layout_marginStart="16dp"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintStart_toStartOf="parent" />

Activity:

private val mapMatcherResultObserver = object : MapMatcherResultObserver {
    override fun onNewMapMatcherResult(mapMatcherResult: MapMatcherResult) {
        ifNonNull(mapMatcherResult.speedLimit) {
            speedLimitView.setSpeedLimit(it)
        } ?: speedLimitView.hide()
    }
}

v2

To replace SpeedLimitView, add MapboxSpeedLimitView to your layout and update data using MapboxSpeedLimitApi:

XML layout:

<com.mapbox.navigation.ui.speedlimit.view.MapboxSpeedLimitView
    android:id="@+id/speedLimitView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="8dp"
    android:layout_marginBottom="16dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintBottom_toBottomOf="parent">

Activity:

// The data in the MapboxSpeedLimitView is formatted by different
// formatting implementations. Below is the default formatter using
// default options but you can use your own formatting classes.
val speedLimitFormatter =  SpeedLimitFormatter(this)

// API used for formatting speed limit related data.
val speedLimitApi = MapboxSpeedLimitApi(speedLimitFormatter)

// Gets notified with location updates.
private val locationObserver = object : LocationObserver {
    override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
        val speedLimitValue = speedLimitApi.updateSpeedLimit(locationMatcherResult.speedLimit)
        binding.speedLimitView.render(speedLimitValue)
    }
}

For more details on adding the speed limit UI component to your application, follow the instructions in the Speed limit guide.

guide
Speed limit

Read more about how the speed limit component works in the Navigation SDK v2.

example
Render speed limit for a route

Render the speed limit of the current road using the Speed Limit API and MapboxSpeedLimitView.

GuidanceViewImageProvider and GuidanceViewListener were replaced

In v2, the junction view interface replaces v1's GuidanceViewImageProvider and GuidanceViewListener.

The junction view interface has several components that are responsible for updating the junction data and displaying it on the screen. MapboxJunctionView allows you to quickly integrate junction views in your app.

For more customized requirements you can use the MapboxJunctionApi to retrieve the data and render it on your own.

In v2, the voice interface replaces v1's NavigationSpeechPlayer. There's no replacement for v1's VoiceInstructionLoader in v2.

The voice interface involves the a few components for playing prompt and detailed voice instructions. Use the MapboxSpeechApi to generate an announcement based on VoiceInstructions objects. Then pass the resulting object to theMapboxVoiceInstructionsPlayer, a text-to-speech engine implementation, to be played. It will use either the VoiceInstructionsFilePlayer or VoiceInstructionsTextPlayer speech player depending on if a synthesized speech mp3 is provided or not.

v1

Drop-in UI: This interface was included in v1's pre-built UI (NavigationView). In v2 you will have to add it to your application manually.

NavigationSpeechPlayer component: If you used NavigationSpeechPlayer directly in v1, your implementation likely looks something like this:

Activity:

private lateinit var speechPlayer: NavigationSpeechPlayer

override fun onMapReady(mapboxMap: MapboxMap) {
  ...
  initializeSpeechPlayer()
}

private fun initializeSpeechPlayer() {
    val cache = Cache(File(application.cacheDir, VOICE_INSTRUCTION_CACHE), 10 * 1024 * 1024)
    val voiceInstructionLoader =
        VoiceInstructionLoader(application, Mapbox.getAccessToken(), cache)
    val speechPlayerProvider =
        SpeechPlayerProvider.Builder(
            application, Locale.US.language, true, voiceInstructionLoader
        ).build()
    speechPlayer = NavigationSpeechPlayer(speechPlayerProvider)
}

override fun onNewVoiceInstructions(voiceInstructions: VoiceInstructions) {
    speechPlayer.play(voiceInstructions)
}

v2

To replace NavigationSpeechPlayer:

Activity:

private lateinit var speechApi: MapboxSpeechApi
private lateinit var voiceInstructionsPlayer: MapboxVoiceInstructionsPlayer

speechApi = MapboxSpeechApi(
    this,
    getString(R.string.mapbox_access_token),
    Locale.US.language
)
voiceInstructionsPlayer = MapboxVoiceInstructionsPlayer(
    this,
    getString(R.string.mapbox_access_token),
    Locale.US.language
)

private val voiceInstructionsObserver =       
  VoiceInstructionsObserver { voiceInstructions ->
    speechApi.generate(voiceInstructions, speechCallback)
}

private val speechCallback =
  MapboxNavigationConsumer<Expected<SpeechError, SpeechValue>> { expected ->
      expected.fold(
          { error ->
              // play the instruction via fallback text-to-speech engine
              voiceInstructionsPlayer.play(
                  error.fallback,
                  voiceInstructionsPlayerCallback
              )
          },
          { value ->
              // play the sound file from the external generator
              voiceInstructionsPlayer.play(
                  value.announcement,
                  voiceInstructionsPlayerCallback
              )
          }
      )
}

private val voiceInstructionsPlayerCallback =
  MapboxNavigationConsumer<SpeechAnnouncement> { value ->
      // remove already consumed file to free-up space
      speechApi.clean(value)
}

For more details on adding voice instructions to your application, follow the instructions in the Voice instructions guide.

guide
Voice instructions

Read more about how voice instructions work in the Navigation SDK v2.

example
Play voice instructions for a route

Use the example to play voice instructions using Speech API and Voice Instruction Player.

The Mapbox Maps SDK v10 removes the CameraModes that were part of the LocationComponent and used to order the map camera to follow the location puck. In v2, the concepts of updating the puck’s position and updating the camera’s position are completely decoupled.

With Maps SDK v10, all camera properties can be manipulated independently and in parallel, so there is no limit to how you can build a custom system that tracks the puck’s position in navigation scenarios. This wasn’t the case with Maps SDK v9 and earlier that was the cause for the introduction of CameraModes that aren’t needed anymore.

The Navigation SDK v2 still allows you to integrate a camera system that tracks the puck’s location and this is where NavigationCamera and the concept of the ViewportDataSource are introduced.

NavigationCamera is a class that tries to simplify management of the Map's camera object in typical navigation scenarios, maintains a state (IDLE, FOLLOWING, or OVERVIEW), and executes camera transitions. NavigationCamera does not produce any camera position values, the positions that the camera should transition to are generated by the ViewportDataSource interface. Mapbox also provides a default MapboxNavigationViewportDataSource implementation that produces opinionated camera positions based on the inputted data.

The new NavigationCamera also replaces the legacy Navigation SDK implementations of DynamicCamera and SimpleCamera that were wrappers on top of the Maps SDK CameraModes.

Below, see a few common uses of v1 LocationComponent camera usage examples and how they can be recreated or improved with the NavigationCamera and the MapboxNavigationViewportDataSource.

Initialize the camera

v2

viewportDataSource = MapboxNavigationViewportDataSource(mapView.getMapboxMap())
navigationCamera = NavigationCamera(
    mapView.getMapboxMap(),
    mapView.getCameraAnimationsPlugin(),
    viewportDataSource
)

Start tracking user location

v1

// Transition to the last known location sample that the
// LocationComponent knows (or do nothing if not available)
locationComponent.setCameraMode(cameraMode)

v2

// Provide the MapboxNavigationViewportDataSource with
// location data
viewportDataSource.onLocationChanged(location)
viewportDataSource.evaluate()

// Then initialize the `FOLLOWING` state
navigationCamera.requestNavigationCameraToFollowing()

In v2, the location can be obtained from the navigation's LocationObserver if the trip session has already been started (MapboxNavigation#startTripSession), or from any LocationEngine instance. You can also fetch the initial data directly from the LocationComponentPlugin's LocationProvider.

Track user location with bearing-matching location

v1

Any location update pushed to the LocationComponent after setting the CameraMode would update the camera.

v2

// Update the viewport data source to trigger a camera
// update when NavigationCamera is in a FOLLOWING state.
viewportDataSource.onLocationChanged(location)
viewportDataSource.evaluate()

Track user location with locked bearing

v1

// Tracking user location while having the camera
// pointing to the north was a separate state.
locationComponent.setCameraMode(CameraMode.TRACKING_GPS_NORTH)

v2

// To detach the camera's bearing from the location
// use the property overrides.
viewportDataSource.followingBearingPropertyOverride(0.0)
viewportDataSource.evaluate()

In v2, when followingBearingPropertyOverride is set, the MapboxNavigationViewportDataSource will not change the value of the overridden property. This means that now you can not only lock the camera's bearing to the north while tracking but also to any other value.

To reset back to location bearing matching, clear the override.

viewportDataSource.followingBearingPropertyOverride(null)
viewportDataSource.evaluate()

Change the zoom level, pitch, or padding

Zoom level, pitch, and padding manipulation operations have the same before and now changes, only the property name changes. Let's take zoom as an example.

v1

The zoom level could be changed via the locationComponent.zoomWhileTracking tracking method. The method only worked if CameraMode was already set to one of the tracking modes, otherwise, the request was lost.

The zoom level could also be changed by overriding the com.mapbox.navigation.ui.camera.Camera#zoom method and recomputing the value that Navigation SDK requested.

v2

Now, the zoom level control can either be left to the MapboxNavigationViewportDataSource which will have a constant zoom level when in a free-drive mode (when there is no route present) and a dynamic zoom level when we're in an active guidance session, or overridden and controlled manually.

To let the default viewport data source manage the zoom when in an active guidance session, provide an active route via the:

viewportDataSource.onRouteChanged(route)

This is often used with the RoutesObserver:

private val routesObserver = object : RoutesObserver {
    override fun onRoutesChanged(routes: List<DirectionsRoute>) {
        if (routes.isNotEmpty()) {
            viewportDataSource.onRouteChanged(routes.first())
            viewportDataSource.evaluate()
        } else {
            viewportDataSource.clearRouteData()
            viewportDataSource.evaluate()
        }
    }
}

And provide the RouteProgress via:

viewportDataSource.onRouteProgressChanged(routeProgress)

This is often used with the RouteProgressObserver:

private val routeProgressObserver = object : RouteProgressObserver {
    override fun onRouteProgressChanged(routeProgress: RouteProgress) {
        viewportDataSource.onRouteProgressChanged(routeProgress)
        viewportDataSource.evaluate()
    }
}

When a route and progress are available, the viewport data source will always try to produce camera positions that best frame the upcoming maneuvers together with the user's location from viewportDataSource.onLocationChanged(location).

But you can also always manually control the zoom level by overriding the zoom property:

viewportDataSource.followingZoomPropertyOverride(16.5)
viewportDataSource.evaluate()

Clear route data

Remember to call viewportDataSource.clearRouteData() whenever the active route is cleared to prevent the default viewport data source from trying to frame the route's geometry that's not visible anymore. For example:

val routesObserver = object : RoutesObserver {
    override fun onRoutesChanged(routes: List<DirectionsRoute>) {
        if (routes.isNotEmpty()) {
            viewportDataSource.onRouteChanged(routes.first())
            viewportDataSource.evaluate()
        } else {
            viewportDataSource.clearRouteData()
            viewportDataSource.evaluate()
        }
    }
}
mapboxNavigation.registerRoutesObserver(routesObserver)

Add padding

The default MapboxNavigationViewportDataSource does not apply any padding. You can apply the padding for framed geometries with:

viewportDataSource.followingPadding(padding)
viewportDataSource.evaluate()

and

viewportDataSource.overviewPadding(padding)
viewportDataSource.evaluate()

Control puck's position on screen

When in the FOLLOWING state, the first point of the framed geometry list will be placed at the bottom edge of this padding, centered horizontally. This typically refers to the user location provided by the viewportDataSource.onLocationChanged(location), if available.

Controlling the position of the puck on screen does not happen via the map's padding value anymore, but by the padding provided to the viewportDataSource.followingPadding. Make sure to read the inline documentation of the property.

Camera transition collisions and navigation gestures management

Both the legacy CameraModes and the new NavigationCamera assume full ownership of the camera object while not idle. This means that by design, any outside transition (like MapboxMap#easeTo and others) would compete with the internal transitions of the navigation camera features.

v1

Whenever any other transition was scheduled CameraMode would fallback to NONE but you could use the LocationComponentOptions#trackingGesturesManagement to prevent gestures like double-tap to zoom in, two-tap to zoom out, or quick sale from breaking the tracking state.

v2

By default, the NavigationCamera only falls back to the IDLE state when another transition interrupts the navigation camera's scheduled transition. However, in practice, developers should use the CameraAnimationsLifecycleListener to avoid competing transitions.

Mapbox provides two default implementations of that interface:

  • NavigationBasicGesturesHandler, which requests the NavigationCamera to IDLE whenever any outside transition or gesture interaction happens.
  • NavigationScaleGestureHandler, which requests the NavigationCamera to IDLE whenever any outside transition happens but allows for executing navigation gestures described above, same as LocationComponentOptions#trackingGesturesManagement did in the past.

You can enable those implementations by registering the lifecycle listeners:

mapView.getCameraAnimationsPlugin().addCameraAnimationsLifecycleListener(
    NavigationScaleGestureHandler(
        context,
        navigationCamera,
        mapView.getMapboxMap(),
        mapView.gestures,
        mapView.location,
        object : NavigationScaleGestureActionListener {
            override fun onNavigationScaleGestureAction() {
                viewportDataSource.options.followingFrameOptions.zoomUpdatesAllowed = false
            }
        }
    ).apply { initialize() }
)

Manage zoom level updates after gesture interaction

When an allowed scale gesture interaction happens and the zoom level changes, the navigation camera would try to reset that zoom level back to the opinionated value computed via the default viewport data source provider as soon as another MapboxNavigationViewportDataSource#evaluate call is made. To prevent that, notice the:

object : NavigationScaleGestureActionListener {
    override fun onNavigationScaleGestureAction() {
        viewportDataSource.options.followingFrameOptions.zoomUpdatesAllowed = false
    }
}

above. This prevents the viewport data source from producing any zoom values after the gesture interaction happens. Reset the followingFrameOptions.zoomUpdatesAllowed value whenever appropriate to restore the dynamic (or overridden) zoom level updates.

Route overview

v1

The CameraModes did not offer any means of showing the overview of the route in the context of the position of the puck. The legacy Navigation SDK APIs did have a concept of an overview, but it also did not take into account the current user's location.

v2

NavigationCamera offers an OVERVIEW state available via navigationCamera.requestNavigationCameraToOverview(). When paired with the MapboxNavigationViewportDataSource, the overview state will always frame the whole route, or if the RouteProgress is also being provided it will frame the remainder of the route. Here's how an overview with padding can be requested:

private val routesObserver = object : RoutesObserver {
    override fun onRoutesChanged(routes: List<DirectionsRoute>) {
        if (routes.isNotEmpty()) {
            viewportDataSource.onRouteChanged(routes.first())
            viewportDataSource.overviewPadding = overviewEdgeInsets
            viewportDataSource.evaluate()
            navigationCamera.requestNavigationCameraToOverview()
        } else {
            viewportDataSource.clearRouteData()
            viewportDataSource.evaluate()
        }
    }
}

Then, on each RouteProgress update, the camera position will be recalculated to only frame the remainder of the route:

private val routeProgressObserver = object : RouteProgressObserver {
    override fun onRouteProgressChanged(routeProgress: RouteProgress) {
        viewportDataSource.onRouteProgressChanged(routeProgress)
        viewportDataSource.evaluate()
    }
}

Frame other points

v1

CameraModes did not allow for framing any arbitrary points in the camera's frame. We had to rely on the location and zoom level to manipulate what's visible on the map.

v2

The MapboxNavigationViewportDataSource allows for passing an arbitrary list of points and the API will make sure that all of those points are visible in the frame together with the provided location and route (if present), as long as the zoom level property is not overridden. Additional points that you'd like to be captured in the camera's frame can be passed via:

viewportDataSource.additionalPointsToFrameForFollowing(points)
viewportDataSource.evaluate()

This can be powerful when paired with bearing manipulation, like:

val center = mapboxMap.cameraState.center
viewportDataSource.additionalPointsToFrameForFollowing(listOf(lookAtPoint))
viewportDataSource.followingBearingPropertyOverride(
    TurfMeasurement.bearing(center, lookAtPoint)
)
viewportDataSource.evaluate()

In active guidance mode, the above operation would frame the current location, the upcoming maneuver, the lookAtPoint but also changes the bearing of the camera to head towards that new point.

Options

Make sure to browse through the MapboxNavigationViewportDataSource.options and read about customization to the generated camera positions and how you can tweak the algorithms and change the user experience.

Mapbox Electronic Horizon

Mapbox Electronic Horizon is in public beta

The Mapbox Electronic Horizon feature of the Mapbox Navigation SDK is in public beta and is subject to changes, including its pricing. Use of the feature is subject to the beta product restrictions in the Mapbox Terms of Service.

Mapbox reserves the right to end any free tier or free evaluation offers at any time and require customers to place an order to purchase the Mapbox Electronic Horizon feature, regardless of the level of use of the feature.

The electronic horizon API was changed. You can use both callbacks and direct calls to get the electronic horizon data. With callbacks you get only the most important data, which can be more efficient and save resources. If you need additional data (for example: edge metadata, edge shape, road object metadata, etc.) you can make a direct call.

guide
Electronic horizon

Read more about how electronic horizon works in the Navigation SDK v2.