Skip to Content
ExamplesIndoor Navigation

Indoor Navigation

These examples cover the full lifecycle of an active navigation session: starting navigation, displaying turn-by-turn instructions, handling off-route events, and cleaning up when the user arrives. Navigation state is driven by the RoutingStatus values emitted by the position repository.

Full Indoor Navigation Example

This example brings together route calculation, map display, and real-time status monitoring into a single flow. The RoutingStatus enum controls which action is taken on each location update.

Why withTimeoutOrNull: Indoor positioning requires beacon signals that may take a few seconds to resolve. The timeout prevents the UI from waiting indefinitely if the user is in an area without coverage.

Why newCurrentUserPosition: This method updates the map’s awareness of the user’s position and synchronizes the rendered route (advancing the progress indicator along completed steps).

class IndoorNavigationFragment : Fragment() { private var lzMap: LazarilloMap? = null private val repoBuilder = RepoBuilder() private fun startNavigation(destinationPlaceId: String, destinationLevel: Int) { viewLifecycleOwner.lifecycleScope.launch { val positionRepo = repoBuilder.getRoutingStatusRepoReference() ?: return@launch // Get current position val currentLocation = withTimeoutOrNull(5000L) { positionRepo.locatorResultFlow .filter { it?.location != null } .mapNotNull { it?.location } .first() } ?: run { showError("Could not determine your location") return@launch } // Calculate route val destination = IdPlaceLocation( id = destinationPlaceId, level = destinationLevel ) val response = LzSdkManager.getRoute( initialLocation = currentLocation, finalLocation = destination, preferAccessibleRoute = false, language = "en" ) if (!response.isSuccessful) { showError("Route calculation failed") return@launch } val route = response.body()?.firstOrNull() ?: return@launch // Display and start navigation LzSdkManager.setCurrentShowingRoute(route) lzMap?.addRoute(route) { println("Route ready -- starting navigation") } // Follow user location lzMap?.followUserLocation( value = true, pitch = 45.0, zoom = 18.0 ) // Monitor navigation progress positionRepo.locatorResultFlow.collect { data -> data?.let { positionData -> lzMap?.newCurrentUserPosition(positionData) when (positionData.status) { RoutingStatus.ON_ROUTE -> { updateStepInstructions(route, positionData.currentStep) } RoutingStatus.OFF_ROUTE -> { showMessage("Recalculating route...") recalculateRoute(positionData.location, destination) } RoutingStatus.FINISHED -> { showMessage("You have arrived!") endNavigation() } else -> { } } } } } }

Step Instructions Update

The currentStep field on PositionCallbackData is a zero-based index into the route’s step list. Update your instruction UI every time the index changes.

private fun updateStepInstructions(route: SdkRoute, stepIndex: Int?) { stepIndex?.let { index -> val steps = route.getSteps() if (index < steps.size) { val step = steps[index] binding.tvInstruction.text = step.plainInstructions } } }

Off-Route Recalculation

When the user deviates from the planned route, RoutingStatus.OFF_ROUTE is emitted. The recommended response is to call getRoute again from the user’s current position and replace the displayed route.

The recalculation pattern mirrors the initial route calculation: get the current location, request a new route, then call addRoute to replace the old one on the map.

End Navigation Cleanup

Always call clearRoute on both the SDK manager and the map instance when navigation ends. This removes route geometry from the map and resets internal navigation state so subsequent route requests start from a clean baseline.

private fun endNavigation() { LzSdkManager.clearRoute() lzMap?.clearRoute() lzMap?.followUserLocation(false) } }
Last updated on