Implement a basic turn-by-turn navigation experience
Warning
There is a known issue with running this Navigation SDK v2 example on Android emulated devices. While our team works to correct this issue, please use a physical Android device.
Use Navigation SDK's UI components to build a custom turn-by-turn navigation UI. Long press on the map to select a destination to navigate toward.
Assets
This example uses two images:
mapbox_ic_start_navigation.xml
to be used to start navigatingmapbox_ic_end_navigation.xml
to be used to stop navigating
Download the images and add them to the drawable
folder in your Android Studio project:
Download stop image
activity_main.xml
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"> <com.mapbox.maps.MapViewandroid:id="@+id/mapView"android:layout_width="0dp"android:layout_height="0dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"/> <androidx.cardview.widget.CardViewandroid:id="@+id/tripProgressCard"android:layout_width="0dp"android:layout_height="wrap_content"app:cardUseCompatPadding="false"app:cardElevation="@dimen/mapbox_dimen_8dp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintBottom_toBottomOf="parent"android:visibility="gone"><com.mapbox.navigation.ui.tripprogress.view.MapboxTripProgressViewandroid:id="@+id/tripProgressView"android:layout_width="match_parent"android:layout_height="wrap_content" /> <ImageViewandroid:id="@+id/stop"android:layout_width="@dimen/mapbox_dimen_48dp"android:layout_height="@dimen/mapbox_dimen_48dp"android:layout_marginEnd="@dimen/mapbox_dimen_12dp"android:layout_gravity="end|center_vertical"app:srcCompat="@drawable/mapbox_ic_end_navigation"/></androidx.cardview.widget.CardView> <com.mapbox.navigation.ui.maneuver.view.MapboxManeuverViewandroid:id="@+id/maneuverView"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_margin="4dp"android:visibility="gone"app:layout_constraintTop_toTopOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent" /> <com.google.android.material.floatingactionbutton.FloatingActionButtonandroid:id="@+id/start"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="@dimen/mapbox_dimen_16dp"android:background="@color/colorSecondary"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:fabSize="auto"android:src="@drawable/mapbox_ic_start_navigation"android:visibility="gone"/> </androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
import android.animation.ValueAnimatorimport android.annotation.SuppressLintimport android.content.res.Configurationimport android.content.res.Resourcesimport android.location.Locationimport android.os.Bundleimport android.view.View.GONEimport android.view.View.VISIBLEimport android.widget.ImageViewimport android.widget.Toastimport androidx.appcompat.app.AppCompatActivityimport androidx.cardview.widget.CardViewimport androidx.core.content.ContextCompatimport com.google.android.material.floatingactionbutton.FloatingActionButtonimport com.mapbox.android.core.location.LocationEngineProviderimport com.mapbox.android.core.permissions.PermissionsListenerimport com.mapbox.android.core.permissions.PermissionsManagerimport com.mapbox.api.directions.v5.models.BannerInstructionsimport com.mapbox.api.directions.v5.models.DirectionsRouteimport com.mapbox.api.directions.v5.models.RouteOptionsimport com.mapbox.api.directions.v5.models.VoiceInstructionsimport com.mapbox.geojson.Pointimport com.mapbox.maps.*import com.mapbox.maps.plugin.LocationPuck2Dimport com.mapbox.maps.plugin.animation.easeToimport com.mapbox.maps.plugin.animation.getCameraAnimationsPluginimport com.mapbox.maps.plugin.delegates.listeners.OnMapLoadErrorListenerimport com.mapbox.maps.plugin.gestures.GesturesPluginimport com.mapbox.maps.plugin.gestures.OnMapLongClickListenerimport com.mapbox.maps.plugin.gestures.getGesturesPluginimport com.mapbox.maps.plugin.locationcomponent.LocationComponentPluginimport com.mapbox.maps.plugin.locationcomponent.OnIndicatorPositionChangedListenerimport com.mapbox.maps.plugin.locationcomponent.getLocationComponentPluginimport com.mapbox.navigation.base.TimeFormatimport com.mapbox.navigation.base.formatter.DistanceFormatterOptionsimport com.mapbox.navigation.base.internal.extensions.applyDefaultParamsimport com.mapbox.navigation.base.options.NavigationOptionsimport com.mapbox.navigation.base.trip.model.RouteProgressimport com.mapbox.navigation.core.MapboxNavigationimport com.mapbox.navigation.core.directions.session.RoutesObserverimport com.mapbox.navigation.core.internal.formatter.MapboxDistanceFormatterimport com.mapbox.navigation.core.trip.session.BannerInstructionsObserverimport com.mapbox.navigation.core.trip.session.LocationObserverimport com.mapbox.navigation.core.trip.session.MapMatcherResultimport com.mapbox.navigation.core.trip.session.MapMatcherResultObserverimport com.mapbox.navigation.core.trip.session.RouteProgressObserverimport com.mapbox.navigation.core.trip.session.VoiceInstructionsObserverimport com.mapbox.navigation.ui.base.model.Expectedimport com.mapbox.navigation.ui.maneuver.api.ManeuverCallbackimport com.mapbox.navigation.ui.maneuver.api.MapboxManeuverApiimport com.mapbox.navigation.ui.maneuver.api.StepDistanceRemainingCallbackimport com.mapbox.navigation.ui.maneuver.api.UpcomingManeuverListCallbackimport com.mapbox.navigation.ui.maneuver.model.Maneuverimport com.mapbox.navigation.ui.maneuver.model.ManeuverErrorimport com.mapbox.navigation.ui.maneuver.model.StepDistanceimport com.mapbox.navigation.ui.maneuver.model.StepDistanceErrorimport com.mapbox.navigation.ui.maneuver.view.MapboxManeuverViewimport com.mapbox.navigation.ui.maps.camera.NavigationCameraimport com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSourceimport com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSourceOptionsimport com.mapbox.navigation.ui.maps.location.NavigationLocationProviderimport com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowApiimport com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowViewimport com.mapbox.navigation.ui.maps.route.arrow.model.RouteArrowOptionsimport com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineApiimport com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineViewimport com.mapbox.navigation.ui.maps.route.line.model.MapboxRouteLineOptionsimport com.mapbox.navigation.ui.maps.route.line.model.RouteLineimport com.mapbox.navigation.ui.tripprogress.api.MapboxTripProgressApiimport com.mapbox.navigation.ui.tripprogress.view.MapboxTripProgressViewimport com.mapbox.navigation.ui.tripprogress.model.DistanceRemainingFormatterimport com.mapbox.navigation.ui.tripprogress.model.EstimatedTimeToArrivalFormatterimport com.mapbox.navigation.ui.tripprogress.model.PercentDistanceTraveledFormatterimport com.mapbox.navigation.ui.tripprogress.model.TimeRemainingFormatterimport com.mapbox.navigation.ui.tripprogress.model.TripProgressUpdateFormatterimport com.mapbox.navigation.ui.utils.internal.ifNonNullimport com.mapbox.navigation.ui.voice.api.MapboxSpeechApiimport com.mapbox.navigation.ui.voice.api.MapboxVoiceInstructionsPlayerimport com.mapbox.navigation.ui.voice.api.SpeechCallbackimport com.mapbox.navigation.ui.voice.api.VoiceInstructionsPlayerCallbackimport com.mapbox.navigation.ui.voice.model.SpeechAnnouncementimport com.mapbox.navigation.ui.voice.model.SpeechErrorimport com.mapbox.navigation.ui.voice.model.SpeechValueimport java.util.Locale class MainActivity :AppCompatActivity(),OnMapLongClickListener { private lateinit var mapboxMap: MapboxMapprivate lateinit var mapboxNavigation: MapboxNavigationprivate lateinit var locationComponent: LocationComponentPluginprivate lateinit var navigationCamera: NavigationCameraprivate lateinit var viewportDataSource: MapboxNavigationViewportDataSourceprivate lateinit var tripProgressApi: MapboxTripProgressApiprivate lateinit var maneuverApi: MapboxManeuverApiprivate lateinit var speechAPI: MapboxSpeechApiprivate lateinit var permissionsManager: PermissionsManager private var isNavigating = falseprivate var routeLineAPI: MapboxRouteLineApi? = nullprivate var routeLineView: MapboxRouteLineView? = nullprivate var routeArrowView: MapboxRouteArrowView? = nullprivate var voiceInstructionsPlayer: MapboxVoiceInstructionsPlayer? = nullprivate val routeArrowAPI: MapboxRouteArrowApi = MapboxRouteArrowApi()private val navigationLocationProvider = NavigationLocationProvider()private val pixelDensity = Resources.getSystem().displayMetrics.densityprivate val overviewEdgeInsets: EdgeInsets by lazy {EdgeInsets(40.0 * pixelDensity,40.0 * pixelDensity,40.0 * pixelDensity,40.0 * pixelDensity)}private val landscapeOverviewEdgeInsets: EdgeInsets by lazy {EdgeInsets(20.0 * pixelDensity,mapboxMap.getSize().width.toDouble() / 1.75,20.0 * pixelDensity,20.0 * pixelDensity)}private val followingEdgeInsets: EdgeInsets by lazy {EdgeInsets(mapboxMap.getSize().height.toDouble() * 2.0 / 3.0,0.0 * pixelDensity,0.0 * pixelDensity,0.0 * pixelDensity)}private val landscapeFollowingEdgeInsets: EdgeInsets by lazy {EdgeInsets(mapboxMap.getSize().height.toDouble() * 2.0 / 5.0,mapboxMap.getSize().width.toDouble() / 2.0,0.0 * pixelDensity,40.0 * pixelDensity)}private val currentManeuverCallback = object : ManeuverCallback {override fun onManeuver(maneuver: Expected<Maneuver, ManeuverError>) {findViewById<MapboxManeuverView>(R.id.maneuverView)}} private val stepDistanceRemainingCallback = object : StepDistanceRemainingCallback {override fun onStepDistanceRemaining(distanceRemaining: Expected<StepDistance, StepDistanceError>) {when (distanceRemaining) {is Expected.Success -> {findViewById<MapboxManeuverView>(R.id.maneuverView).renderDistanceRemaining(distanceRemaining.value)}is Expected.Failure -> {// Not handled}}}} private val upcomingManeuversCallback = object : UpcomingManeuverListCallback {override fun onUpcomingManeuvers(maneuvers: Expected<List<Maneuver>, ManeuverError>) {when (maneuvers) {is Expected.Success -> {findViewById<MapboxManeuverView>(R.id.maneuverView).renderUpcomingManeuvers(maneuvers.value)}is Expected.Failure -> {// Not handled}}}} private val voiceInstructionsPlayerCallback: VoiceInstructionsPlayerCallback =object : VoiceInstructionsPlayerCallback {override fun onDone(announcement: SpeechAnnouncement) {speechAPI.clean(announcement)}} private val speechCallback = object : SpeechCallback {override fun onSpeech(state: Expected<SpeechValue, SpeechError>) {when (state) {is Expected.Success -> {val currentSpeechValue = state.valuevoiceInstructionsPlayer?.play(currentSpeechValue.announcement,voiceInstructionsPlayerCallback)}is Expected.Failure -> {val currentSpeechError = state.errorvoiceInstructionsPlayer?.play(currentSpeechError.fallback,voiceInstructionsPlayerCallback)}}}} private val mapMatcherResultObserver = object : MapMatcherResultObserver {override fun onNewMapMatcherResult(mapMatcherResult: MapMatcherResult) {val transitionOptions: (ValueAnimator.() -> Unit)? = if (mapMatcherResult.isTeleport) {{duration = 0}} else {{duration = 1000}}navigationLocationProvider.changePosition(mapMatcherResult.enhancedLocation,mapMatcherResult.keyPoints,latLngTransitionOptions = transitionOptions,bearingTransitionOptions = transitionOptions)viewportDataSource.onLocationChanged(mapMatcherResult.enhancedLocation) viewportDataSource.evaluate()if (mapMatcherResult.isTeleport) {navigationCamera.resetFrame()}}} private val routesObserver = object : RoutesObserver {override fun onRoutesChanged(routes: List<DirectionsRoute>) {if (routes.isNotEmpty()) {routeLineAPI?.setRoutes(listOf(RouteLine(routes[0], null)))?.apply {ifNonNull(routeLineView, mapboxMap.getStyle()) { view, style ->view.renderRouteDrawData(style, this)}}viewportDataSource.onRouteChanged(routes[0])if (!isNavigating) {findViewById<FloatingActionButton>(R.id.start).visibility = VISIBLEupdateCameraToOverview()}} else {viewportDataSource.clearRouteData()updateCameraToIdle()clearRouteLine()}}} private val routeProgressObserver = object : RouteProgressObserver {override fun onRouteProgressChanged(routeProgress: RouteProgress) {viewportDataSource.onRouteProgressChanged(routeProgress)viewportDataSource.evaluate()routeArrowAPI.updateUpcomingManeuverArrow(routeProgress).apply {ifNonNull(routeArrowView, mapboxMap.getStyle()) { view, style ->view.render(style, this)}}findViewById<MapboxTripProgressView>(R.id.tripProgressView).render(tripProgressApi.getTripProgress(routeProgress))maneuverApi.getUpcomingManeuverList(routeProgress, upcomingManeuversCallback)ifNonNull(routeProgress.currentLegProgress) { legProgress ->ifNonNull(legProgress.currentStepProgress) {maneuverApi.getStepDistanceRemaining(it, stepDistanceRemainingCallback)}}}} private val bannerInstructionsObserver = object : BannerInstructionsObserver {override fun onNewBannerInstructions(bannerInstructions: BannerInstructions) {if (findViewById<MapboxManeuverView>(R.id.maneuverView).visibility != VISIBLE) {findViewById<MapboxManeuverView>(R.id.maneuverView).visibility = VISIBLE}maneuverApi.getManeuver(bannerInstructions, currentManeuverCallback)}} private val voiceInstructionsObserver = object : VoiceInstructionsObserver {override fun onNewVoiceInstructions(voiceInstructions: VoiceInstructions) {speechAPI.generate(voiceInstructions,speechCallback)}} override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main) mapboxMap = findViewById<MapView>(R.id.mapView).getMapboxMap()checkPermissions {locationComponent =findViewById<MapView>(R.id.mapView).getLocationComponentPlugin().apply {this.locationPuck = LocationPuck2D(bearingImage = ContextCompat.getDrawable(this@MainActivity,R.drawable.mapbox_navigation_puck_icon))setLocationProvider(navigationLocationProvider)enabled = true}initNavigation()viewportDataSource = MapboxNavigationViewportDataSource(MapboxNavigationViewportDataSourceOptions.Builder().build(),mapboxMap)navigationCamera = NavigationCamera(mapboxMap,findViewById<MapView>(R.id.mapView).getCameraAnimationsPlugin(),viewportDataSource)init()tripProgressApi = MapboxTripProgressApi(getTripProgressFormatter())maneuverApi = MapboxManeuverApi(MapboxDistanceFormatter(DistanceFormatterOptions.Builder(this).build()))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 fun checkPermissions(onMapReady: () -> Unit) {if (PermissionsManager.areLocationPermissionsGranted(this@MainActivity)) {onMapReady()} else {permissionsManager = PermissionsManager(object : PermissionsListener {override fun onExplanationNeeded(permissionsToExplain: List<String>) {Toast.makeText(this@MainActivity, "You need to accept location permissions.",Toast.LENGTH_SHORT).show()} override fun onPermissionResult(granted: Boolean) {if (granted) {onMapReady()} else {this@MainActivity.finish()}}})permissionsManager.requestLocationPermissions(this@MainActivity)}} override fun onStart() {super.onStart()findViewById<MapView>(R.id.mapView).onStart()} override fun onStop() {super.onStop()navigationCamera.resetFrame()findViewById<MapView>(R.id.mapView).onStop()} override fun onDestroy() {super.onDestroy()findViewById<MapView>(R.id.mapView).onDestroy()if (::mapboxNavigation.isInitialized) {mapboxNavigation.unregisterRoutesObserver(routesObserver)mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)mapboxNavigation.unregisterMapMatcherResultObserver(mapMatcherResultObserver)mapboxNavigation.unregisterVoiceInstructionsObserver(voiceInstructionsObserver)mapboxNavigation.unregisterBannerInstructionsObserver(bannerInstructionsObserver)}mapboxNavigation.onDestroy()speechAPI.cancel()voiceInstructionsPlayer?.shutdown()} override fun onLowMemory() {super.onLowMemory()findViewById<MapView>(R.id.mapView).onLowMemory()} override fun onMapLongClick(point: Point): Boolean {val currentLocation = navigationLocationProvider.lastLocationif (currentLocation != null) {val originPoint = Point.fromLngLat(currentLocation.longitude,currentLocation.latitude)findRoute(originPoint, point)}return false} @SuppressLint("MissingPermission")private fun init() {initRouteLine()initStyle()initViews()mapboxNavigation.startTripSession()} private fun initRouteLine() {val mapboxRouteLineOptions = MapboxRouteLineOptions.Builder(this).withRouteLineBelowLayerId("road-label").build()routeLineAPI = MapboxRouteLineApi(mapboxRouteLineOptions)routeLineView = MapboxRouteLineView(mapboxRouteLineOptions) val routeArrowOptions = RouteArrowOptions.Builder(this).build()routeArrowView = MapboxRouteArrowView(routeArrowOptions)} private fun initViews() {findViewById<FloatingActionButton>(R.id.start).setOnClickListener {startNavigation()findViewById<FloatingActionButton>(R.id.start).visibility = GONEfindViewById<CardView>(R.id.tripProgressCard).visibility = VISIBLE}findViewById<ImageView>(R.id.stop).setOnClickListener {stopNavigation()findViewById<MapboxManeuverView>(R.id.maneuverView).visibility = GONEfindViewById<CardView>(R.id.tripProgressCard).visibility = GONE}} @SuppressLint("MissingPermission")private fun initNavigation() {mapboxNavigation = MapboxNavigation(NavigationOptions.Builder(this).accessToken(getString(R.string.mapbox_access_token)).locationEngine(LocationEngineProvider.getBestLocationEngine(this)).build()).apply {registerLocationObserver(object : LocationObserver {override fun onRawLocationChanged(rawLocation: Location) {updateCameraToIdle()val point = Point.fromLngLat(rawLocation.longitude, rawLocation.latitude)val cameraOptions = CameraOptions.Builder().center(point).zoom(13.0).pitch(45.0).build()mapboxMap.easeTo(cameraOptions)navigationLocationProvider.changePosition(rawLocation)mapboxNavigation.unregisterLocationObserver(this)} override fun onEnhancedLocationChanged(enhancedLocation: Location,keyPoints: List<Location>) {}})registerRoutesObserver(routesObserver)registerRouteProgressObserver(routeProgressObserver)registerMapMatcherResultObserver(mapMatcherResultObserver)registerVoiceInstructionsObserver(voiceInstructionsObserver)registerBannerInstructionsObserver(bannerInstructionsObserver)}} @SuppressLint("MissingPermission")private fun initStyle() {mapboxMap.loadStyleUri(Style.MAPBOX_STREETS,object : Style.OnStyleLoaded {override fun onStyleLoaded(style: Style) {getGesturePlugin().addOnMapLongClickListener(this@MainActivity)}},object : OnMapLoadErrorListener {override fun onMapLoadError(mapViewLoadError: MapLoadError, msg: String) {}})} private fun findRoute(origin: Point, destination: Point) {mapboxNavigation.requestRoutes(RouteOptions.builder().applyDefaultParams().accessToken(getString(R.string.mapbox_access_token)).coordinates(listOf(origin, destination)).alternatives(true).build())} private fun updateCameraToOverview() {if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {viewportDataSource.overviewPaddingPropertyOverride(landscapeOverviewEdgeInsets)} else {viewportDataSource.overviewPaddingPropertyOverride(overviewEdgeInsets)}viewportDataSource.evaluate()navigationCamera.requestNavigationCameraToOverview()} private fun updateCameraToFollowing() {if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {viewportDataSource.followingPaddingPropertyOverride(landscapeFollowingEdgeInsets)} else {viewportDataSource.followingPaddingPropertyOverride(followingEdgeInsets)}viewportDataSource.evaluate()navigationCamera.requestNavigationCameraToFollowing()} private fun updateCameraToIdle() {navigationCamera.requestNavigationCameraToIdle()} private fun startNavigation() {isNavigating = trueupdateCameraToFollowing()} private fun stopNavigation() {isNavigating = falseupdateCameraToIdle()clearRouteLine()} private fun clearRouteLine() {ifNonNull(routeLineAPI, routeLineView, mapboxMap.getStyle()) { api, view, style ->view.renderClearRouteLineValue(style, api.clearRouteLine())}} private fun getGesturePlugin(): GesturesPlugin {return findViewById<MapView>(R.id.mapView).getGesturesPlugin()} private fun getTripProgressFormatter(): TripProgressUpdateFormatter {return TripProgressUpdateFormatter.Builder(this).distanceRemainingFormatter(DistanceRemainingFormatter(mapboxNavigation.navigationOptions.distanceFormatterOptions)).timeRemainingFormatter(TimeRemainingFormatter(this)).percentRouteTraveledFormatter(PercentDistanceTraveledFormatter()).estimatedTimeToArrivalFormatter(EstimatedTimeToArrivalFormatter(this,TimeFormat.NONE_SPECIFIED)).build()}}