Turn-by-turn navigation
Turn-by-turn navigation provides real-time guidance along a calculated route. It informs the user about upcoming maneuvers and tracks progress along the route. To get started, add the Navigation module to your project and initialize a TomTomNavigation object. Refer to the Quickstart guide for setup instructions.
Starting navigation
Once the TomTomNavigation object is initialized, you can start navigation. Turn-by-turn navigation requires NavigationOptions , which must include an active RoutePlan.
A RoutePlan consists of a Route and the corresponding RoutePlanningOptions object. To learn how to plan a Route, see the Planning a route guide.
val routePlan = RoutePlan(route = route, routePlanningOptions = routePlanningOptions)Next, use the prepared RoutePlan to create NavigationOptions, then start navigation by calling the start(NavigationOptions) method on your TomTomNavigation object.
tomTomNavigation.start(NavigationOptions(routePlan))
You can also stop the current navigation session manually using TomTomNavigation.stop(). This clears all related session data:
tomTomNavigation.stop()Updating the route
Once TomTomNavigation is started, the active RoutePlan can be changed at any time—either manually or automatically by the navigation session.
To observe changes to the route during navigation, use the following listeners:
RouteUpdatedListener2: triggered when the route is updated. SeeRouteUpdatedReasonfor more details.RouteAddedListener: triggered when a new route is added to the session.RouteRemovedListener: triggered when a route is removed from the session.ActiveRouteChangedListener: triggered when a new route is selected as the active one. The route must already be added to the session.
To manually update the active RoutePlan call:
tomTomNavigation.setActiveRoutePlan(routePlan)val routeAddedListener = RouteAddedListener { route: Route, options: RoutePlanningOptions, reason: RouteAddedReason -> // YOUR CODE GOES HERE }val routeRemovedListener = RouteRemovedListener { route: Route, reason: RouteRemovedReason -> // YOUR CODE GOES HERE }val activeRouteChangedListener = ActiveRouteChangedListener { route: Route -> // YOUR CODE GOES HERE }val routeUpdatedListener = RouteUpdatedListener2 { route: Route, reason: RouteUpdatedReason -> // YOUR CODE GOES HERE }tomTomNavigation.addRouteAddedListener(routeAddedListener)tomTomNavigation.addRouteRemovedListener(routeRemovedListener)tomTomNavigation.addActiveRouteChangedListener(activeRouteChangedListener)tomTomNavigation.addRouteUpdatedListener2(routeUpdatedListener)To remove previously-added listeners, call the appropriate methods on the TomTomNavigation.
tomTomNavigation.removeRouteAddedListener(routeAddedListener)tomTomNavigation.removeRouteRemovedListener(routeRemovedListener)tomTomNavigation.removeActiveRouteChangedListener(activeRouteChangedListener)tomTomNavigation.removeRouteUpdatedListener2(routeUpdatedListener)Modifying the RoutePlanningOptions of the active route
When using the Extended flavor
The TomTom Maps and Navigation SDK offers extensions that allow users to modify the active route’s RoutePlanningOptions. These extensions are added to support the following use cases:
- Skipping an unvisited
RouteStop. Note: The final destination cannot be skipped. - Skipping a charging station using soft avoid. In this case, the
RoutePlannerattempts to calculate a route that avoids the specified charging station. However, if no reachable route can be found without visiting it, the station may still be included.
To skip a route stop, retrieve the latest RoutePlanningOptions, replan the route, and update navigation using the setActiveRoutePlan method. The following snippet shows how to do this:
val navigationSnapshot = tomTomNavigation.navigationSnapshot
val routeStopThatShouldBeSkipped = requireNotNull(navigationSnapshot!!.routes.first().waypoints.first()) { "Navigation should not be stopped and there should be a waypoint that the user wants to skip." }
// Create modified route planning options for the active route.val modifiedRoutePlanningOptions = navigationSnapshot.currentActiveRoutePlanningOptions .skipRouteStop(routeStopThatShouldBeSkipped)
// Create a route planning callback that will change the active route plan if successfully planned.val routePlanningCallback = object : RoutePlanningCallback { override fun onSuccess(result: RoutePlanningResponse) { // Choose a route, the first one is the best and usually fits the needs of the user. val chosenRoute = result.routes.first()
tomTomNavigation.setActiveRoutePlan( RoutePlan( route = chosenRoute, routePlanningOptions = modifiedRoutePlanningOptions, ), ) }
override fun onFailure(failure: RoutingFailure) { // Handle failure }
override fun onRoutePlanned(route: Route) { // Your code goes here }}
// Plan a modified routeval cancellable = routePlanner.planRoute(modifiedRoutePlanningOptions, routePlanningCallback)Route progress
Position-dependent fields that describe the user’s navigation status are collectively referred to as route progress data. Examples include the current position along the route (as an offset from the start), remaining travel time, and distance to the destination. These values are updated on every position update—typically once per second for each followed route. You can listen for changes to route progress. To listen for changes to route progress, set a ProgressUpdatedListener on the TomTomNavigation object. The ProgressUpdatedListener is triggered whenever the user’s progress along the Route changes. It provides a RouteProgressobject containing useful metrics such as estimated arrival time and remaining distance.
val progressUpdatedListener = ProgressUpdatedListener { progress: RouteProgress -> // YOUR CODE GOES HERE }tomTomNavigation.addProgressUpdatedListener(progressUpdatedListener)To remove a previously added listener, call TomTomNavigation.removeProgressUpdatedListener(ProgressUpdatedListener).
tomTomNavigation.removeProgressUpdatedListener(progressUpdatedListener)Route deviations
During navigation, the TomTomNavigation object tracks the user’s position relative to the navigated routes and determines which ones are currently being followed. To listen for updates about route tracking changes, use the RouteTrackingStateUpdatedListener.
This listener provides a RouteTrackingState object, which contains lists of followed and unfollowed routes. It also indicates whether the driver has deviated from all tracked routes. To verify if the driver deviated from the route, check if the RouteTrackingState.hasDeviated property is true.
To listen for the route tracking updates, set a RouteTrackingStateUpdatedListener on the TomTomNavigation object.
val routeTrackingStateUpdatedListener = RouteTrackingStateUpdatedListener { routeTrackingState: RouteTrackingState -> // YOUR CODE GOES HERE }tomTomNavigation.addRouteTrackingStateUpdatedListener(routeTrackingStateUpdatedListener)To remove RouteTrackingStateUpdatedListener use the TomTomNavigation.removeRouteTrackingStateUpdatedListener(RouteTrackingStateUpdatedListener) method.
tomTomNavigation.removeRouteTrackingStateUpdatedListener(routeTrackingStateUpdatedListener)If the driver deviates from the active route, navigation automatically enters free driving mode. In this mode, navigation temporarily operates without a RoutePlan. However, it automatically attempts to calculate a new route using the same cost model as the original. If successful, this new route becomes the active RoutePlan. You can find more details about automatic replanning in the Replanning on deviation section.
Route guidance
Route guidance combines maneuver instructions and spoken announcements that help users navigate along a route. Historically, the only source of voice announcements in the Navigation SDK has been turn-by-turn guidance, provided through the GuidanceUpdatedListener. The GuidanceUpdatedListener delivers maneuver instructions and announcements directly tied to those maneuvers. As the SDK has evolved, it now supports more audible use cases such as traffic jam alerts, better route proposals, and soon other contextual announcements that are not part of route-bound guidance. To support these broader scenarios, we introduced a new interface called the AnnouncementListener. The AnnouncementListener serves as a unified entry point for all audible announcements, making it easier for applications to receive, manage, and prioritize them while avoiding playback collisions.
Comparison: GuidanceUpdatedListener vs AnnouncementListener
| Capability | GuidanceUpdatedListener | AnnouncementListener |
Primary purpose | Maneuver instructions and guidance-bound updates | Announcements only (guidance and non-guidance) |
Callback |
|
|
Multiple announcement types | Limited to guidance | Yes, all supported types (e.g., traffic jam, better route) |
Audio collision management | Not integrated | Integrated via |
Free driving support | Not delivered | Warnings delivered (e.g., traffic jam alerts) |
Backward compatibility | Existing apps continue to work | Recommended path forward |
GuidanceUpdatedListener
During navigation, TomTomNavigation generates guidance updates after each location change. These updates include upcoming maneuver instructions, the distance to the next maneuver, and announcements when the distance is within a triggerable range. All updates are sent to a GuidanceUpdatedListener. Note: Set the GuidanceUpdatedListener before starting the TomTomNavigation session.
The GuidanceUpdatedListener interface provides three callback methods:
onInstructionsChanged(List<GuidanceInstruction>)– Triggered when guidance instructions change. EachGuidanceInstructiondescribes a specific maneuver (e.g., Turn right, Keep left, Take the motorway).onAnnouncementGenerated(GuidanceAnnouncement, Boolean)– Triggered when an announcement is generated.
A GuidanceAnnouncement contains a message, geographic location, and distance to the instruction point. The shouldPlay flag indicates whether announcement should be triggered according to guidelines.
onDistanceToNextInstructionChanged(Distance, List<GuidanceInstruction>, InstructionPhase)– Triggered whenever the distance to the next instruction changes. It provides:- The current distance to the next maneuver (in meters)
- The list of upcoming instructions
- The current InstructionPhase
val guidanceUpdatedListener = object : GuidanceUpdatedListener { override fun onInstructionsChanged(instructions: List<GuidanceInstruction>) { // YOUR CODE GOES HERE }
override fun onAnnouncementGenerated( announcement: GuidanceAnnouncement, shouldPlay: Boolean, ) { // YOUR CODE GOES HERE }
override fun onDistanceToNextInstructionChanged( distance: Distance, instructions: List<GuidanceInstruction>, currentPhase: InstructionPhase, ) { // YOUR CODE GOES HERE } }tomTomNavigation.addGuidanceUpdatedListener(guidanceUpdatedListener)To remove a previously added GuidanceUpdatedListener, use the TomTomNavigation.removeGuidanceUpdatedListener(GuidanceUpdatedListener) method.
tomTomNavigation.removeGuidanceUpdatedListener(guidanceUpdatedListener)To change the language used for guidance instructions, set the TomTomNavigation.preferredLanguage property:
tomTomNavigation.preferredLanguage = Locale.FRANCEYou can retrieve the current language using the TomTomNavigation.language property.
AnnouncementListener
During active guidance, TomTomNavigation generates maneuver instructions and warnings (such as traffic jam alerts). In free driving mode, it generates only warnings. All of these are delivered as audio announcements. These announcements are sent to a AnnouncementListener at a time when announcements should be played. Note: Set the AnnouncementListener before starting the TomTomNavigation session.
The AnnouncementListener interface provides one callback method:
onAnnouncementsGenerated(announcements: List<Announcement>)– Triggered when announcements that should be played have been generated.
val announcementListener = object : AnnouncementListener { override fun onAnnouncementsGenerated(announcements: List<Announcement>) { // YOUR CODE GOES HERE } }tomTomNavigation.addAnnouncementListener(announcementListener)To remove a previously added AnnouncementListener, use the TomTomNavigation.removeAnnouncementListener(AnnouncementListener) method.
tomTomNavigation.removeAnnouncementListener(announcementListener)Announcement types
TomTomNavigation generates multiple types of announcements, including guidance announcements, better route proposals, and traffic jam alerts.
Each announcement is represented by a subtype of the Announcement class. This allows you to perform type-specific handling of announcements.
announcements.forEach { announcement -> when (announcement) { is BetterRouteProposalAnnouncement -> { // Handle better route proposal announcement }
is TrafficJamAnnouncement -> { // Handle traffic jam announcement }
else -> { // Handle other types of announcements } }}Every announcement includes Message that can be either:
VerbalMessage- aMessageintended for text-to-speech synthesis.Alert- aMessagewith no text to be spoken, typically used to trigger a sound.
In addition, some announcements provide extra information that can be used to enhance the user experience. For example, a BetterRouteProposalAnnouncement includes the routeId of the proposed better route, which can be used to suppress announcements for the previously declined better route.
Audio collision management
When multiple announcements are generated simultaneously, it is crucial to manage audio playback effectively to ensure clarity and prevent overlapping announcements. TomTomNavigation incorporates an audio collision management system that prioritizes announcements based on their importance and timing.
It is possible to choose between two strategies for handling audio collisions:
-
NoResolution- No resolution is applied to announcement conflicts, thereby making theAnnouncementListenerreturn all announcements that should be played, without any filtering and in no particular order. -
PriorityCancellation- Announcements are prioritized based on their importance, and lower-priority announcements are canceled if they conflict with higher-priority ones.
To set the desired strategy, use the TomTomNavigation.announcementConflictResolutionStrategy property:
tomTomNavigation.announcementConflictResolutionStrategy = AnnouncementConflictResolutionStrategy.PriorityCancellationor
tomTomNavigation.announcementConflictResolutionStrategy = AnnouncementConflictResolutionStrategy.NoResolutionMigrate from GuidanceUpdatedListener to AnnouncementListener
To migrate to the AnnouncementListener from the GuidanceUpdatedListener, while leaving the other logic intact, you can use the following code:
fun GuidanceAnnouncement.toAnnouncementType(): AnnouncementType { return when (this) { is GuidanceAnnouncement.Follow -> AnnouncementType.GuidanceFollow is GuidanceAnnouncement.Main -> AnnouncementType.GuidanceMain is GuidanceAnnouncement.Early -> AnnouncementType.GuidanceEarly is GuidanceAnnouncement.Confirmation -> AnnouncementType.GuidanceConfirmation is GuidanceAnnouncement.FarAway -> AnnouncementType.GuidanceFarAway else -> throw IllegalArgumentException("Unsupported announcement type: $this") }}
fun GuidanceAnnouncement.toLegacyGuidanceAnnouncement(): LegacyGuidanceAnnouncement { return LegacyGuidanceAnnouncement( id = this.id, announcementType = this.toAnnouncementType(), ssmlMessage = this.message.ssmlMessage, language = this.message.language, )}
announcements .filterIsInstance<GuidanceAnnouncement>() .map { it.toLegacyGuidanceAnnouncement() } .let { /* use the same logic from the GuidanceUpdatedListener */ }Lane-level guidance
TomTomNavigation has built-in support for generating lane guidance. Lane guidance is generated for each LaneSection object in a Route.
You can learn more about how to request a route with a LaneSection by reading the Route sections guide.
The LaneGuidance object includes:
lanes- A left‑to‑right list ofLaneobjects, each representing one physical lane on the road. EachLanecontains:directions- A list of possible driving directions available from the lane.
- Examples:
LEFT,STRAIGHT,RIGHT, etc. - At least one direction is expected.
follow- The recommended direction to follow if this lane is part of the active route.
nullmeans the lane is not recommended.- If not
null, the driver should follow that direction from this lane.
- More than one lane can be marked as recommended.
Example:
// Only a left arrow, not recommendedLane( directions = listOf(Direction.LEFT), follow = null,)
// Straight + right arrows, straight is recommendedLane( directions = listOf(Direction.STRAIGHT, Direction.RIGHT), follow = Direction.STRAIGHT,)laneSeparators- A list of separators (e.g., dashed lines, solid barriers) shown between and around lanes. For n lanes, n + 1 separators are provided. Each separator is shared between two lanes or marks the road edge.routeOffset- The distance from the start of the route to where this lane guidance begins.length- The distance over which this lane guidance is valid.
The following are the possible states for each individual lane guidance arrow

Highlighted SLG arrow represents the direction required by the current route. Greyed-out arrows represent alternative directions that should not be taken.
The following schematic images help illustrate Lane‑level Guidance:
|
|
- The right image shows the schematic lane arrow display, highlighting which lanes are part of the recommended maneuver. In this example, three right-turn lanes are highlighted.
- The left image provides a realistic visualization of the road ahead. It shows a highway segment with clearly visible lanes and lane separators, helping developers understand how the guidance maps to actual road structures.
The generated LaneGuidance is sent to LaneGuidanceUpdatedListener. LaneGuidanceUpdatedListener has two methods:
onLaneGuidanceStarted(LaneGuidance)- Triggered when lane guidance appears.onLaneGuidanceEnded(LaneGuidance)- Triggered when lane guidance disappears.
The onLaneGuidanceStarted(LaneGuidance) callback is only triggered when all of the following conditions are met:
- A maneuver is approaching (e.g., turn, merge, exit)
- More than one lane is available (
lanes.size > 1) - Each lane includes at least one direction
- At least one lane has a
followdirection - Not all lanes have a
followdirection
If these conditions are not met, the callback is not triggered.
val laneGuidanceUpdatedListener = object : LaneGuidanceUpdatedListener { override fun onLaneGuidanceStarted(laneGuidance: LaneGuidance) { // laneGuidance.lanes is guaranteed to be non‑empty // YOUR CODE GOES HERE }
override fun onLaneGuidanceEnded(laneGuidance: LaneGuidance) { // YOUR CODE GOES HERE } }tomTomNavigation.addLaneGuidanceUpdatedListener(laneGuidanceUpdatedListener)To remove a previously added LaneGuidanceUpdatedListener, use the TomTomNavigation.removeLaneGuidanceUpdatedListener(LaneGuidanceUpdateListener) method.
tomTomNavigation.removeLaneGuidanceUpdatedListener(laneGuidanceUpdatedListener)Arrival experience
The Navigation module uses the generated RouteProgress to detect when the user arrives at their destination. When arrival is detected, the DestinationArrivalListener is triggered. This means that the ArrivalDetectionEngine has confirmed arrival.
Note: Even after arrival is detected, navigation remains in turn-by-turn mode until it is manually stopped.
To listen for arrival events, register a DestinationArrivalListener.
val destinationArrivalListener = DestinationArrivalListener { // YOUR CODE GOES HERE }tomTomNavigation.addDestinationArrivalListener(destinationArrivalListener)To remove a previously registered DestinationArrivalListener, use:
tomTomNavigation.removeDestinationArrivalListener(destinationArrivalListener)Waypoint arrival
The active Route may include intermediate stops that the driver intends to visit before reaching the final destination. These intermediate stops, known as waypoints, are listed in routeStops as instances of the RouteStop class. You can find more details on waypoints in the Waypoints and custom routes guide.
Arriving at a waypoint is detected in three phases: Approaching, Visiting, and Departing. When a waypoint’s arrival state changes, the WaypointArrivalListener is triggered. This means the ArrivalDetectionEngine has successfully detected arrival at the waypoint. It determines arrival by verifying whether the distance along the route is within the arrival radius of the waypoint. The distance threshold is:
- 100 meters on motorways
- 50 meters on other roads
A waypoint is automatically marked as departed when:
- The driver is on an active route and has increased the distance along the route by the distance threshold.
- The driver deviates from the active route, moves away from the waypoint by more than the distance threshold in a straight line.
To listen for waypoint arrival and departure events, implement and register a WaypointArrivalListener:
val waypointArrivalListener = object : WaypointArrivalListener { override fun onWaypointArrived( waypoint: RouteStop, route: Route, ) { // YOUR CODE GOES HERE }
override fun onWaypointDeparted( waypoint: RouteStop, route: Route, ) { // YOUR CODE GOES HERE } }tomTomNavigation.addWaypointArrivalListener(waypointArrivalListener)To remove a previously added WaypointArrivalListener, use the following code:
tomTomNavigation.removeWaypointArrivalListener(waypointArrivalListener)You can also manually mark a waypoint as visited independently of the ArrivalDetectionEngineby calling:
tomTomNavigation.departFromWaypoint(waypoint)This method throws an exception if the specified waypoint has not yet been marked as arrived or has no effect if it has already been departed from.
If the operation succeeds, the WaypointArrivalListener.onWaypointDeparted callback is triggered. This signifies completion of arrival detection for the specific waypoint. Navigation then switches to detecting arrival for the next waypoint, if one exists.
Next steps
Since you have learned how to work with turn-by-turn navigation, here are recommendations for the next steps:

