Skip to Content
ExamplesComplete Navigation Flow

Complete Navigation Flow

This example demonstrates the full navigation lifecycle in a single Activity: initializing the SDK, letting the user choose a venue, loading the map with place markers, setting up location tracking, calculating a route, and handling navigation status until the user arrives. It is the recommended reference when building a screen that combines all SDK features.

The flow is divided into five sequential stages, each triggered by the completion of the previous one:

  1. Initialize the SDK.
  2. Load available venues and let the user choose.
  3. Build the map for the selected venue and load place markers.
  4. Set up location tracking with the position repository.
  5. Calculate a route when the user selects a destination and monitor navigation status.
class NavigationActivity : AppCompatActivity() { private var lzMap: LazarilloMap? = null private val repoBuilder = RepoBuilder() private var currentRoute: SdkRoute? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_navigation) // Step 1: Initialize SDK LzSdkManager.initialize( context = applicationContext, apiKey = "YOUR_API_KEY", enableLogging = BuildConfig.DEBUG ) // Step 2: Load available venues loadVenues() } private fun loadVenues() { lifecycleScope.launch { val response = LzSdkManager.getParentPlaces() if (response.isSuccessful) { val venues = response.body() ?: return@launch showVenueSelector(venues) } } } private fun onVenueSelected(venue: SdkPlace) { LzSdkManager.setParentPlace(venue.id!!) setupMap(venue.id!!) } private fun setupMap(placeId: String) { val config = LazarilloMapConfig().apply { parentPlaceId = placeId zoom = 17 minZoom = 15.0 maxZoom = 20.0 hideZoomIn = false hideZoomOut = false hideZoomToLocation = false hideFloorSelector = false trackingConfig = TrackingConfig( enableReactivation = true, reactivationDelayMs = 500L, maxReactivationAttempts = 10 ) } lzMap = LazarilloMap( id = "nav-map", config = config, passedContext = this, inflater = layoutInflater, lifecycleScope = lifecycleScope, positionRepository = null ) { onMapReady(placeId) } findViewById<FrameLayout>(R.id.map_container).addView(lzMap) } private fun onMapReady(placeId: String) { lifecycleScope.launch { // Step 3: Load place markers val subPlaces = LzSdkManager.getSubPlaces() subPlaces.forEach { place -> lzMap?.addPlaceMarker(place) { } } // Step 4: Setup location tracking repoBuilder.buildPositionStatusRepository( context = this@NavigationActivity, parentPlaceId = placeId, useMotionDetection = true, useCompassSensors = true, route = null, repoScope = lifecycleScope ) val positionRepo = repoBuilder.getRoutingStatusRepoReference() positionRepo?.let { repo -> lzMap?.setLocationProvider(repo) lzMap?.toggleLocationComponent(true) } } } fun navigateTo(destinationId: String, level: Int) { lifecycleScope.launch { val positionRepo = repoBuilder.getRoutingStatusRepoReference() ?: return@launch // Get current location val currentLocation = withTimeoutOrNull(5000L) { positionRepo.locatorResultFlow .filter { it?.location != null } .mapNotNull { it?.location } .first() } ?: return@launch // Calculate route val response = LzSdkManager.getRoute( initialLocation = currentLocation, finalLocation = IdPlaceLocation(destinationId, level) ) if (!response.isSuccessful) return@launch val route = response.body()?.firstOrNull() ?: return@launch // Display route currentRoute = route LzSdkManager.setCurrentShowingRoute(route) lzMap?.addRoute(route) { } lzMap?.followUserLocation(true, pitch = 45.0, zoom = 18.0) // Monitor navigation positionRepo.locatorResultFlow.collect { data -> data?.let { lzMap?.newCurrentUserPosition(it) handleNavigationStatus(it) } } } } private fun handleNavigationStatus(data: PositionCallbackData) { when (data.status) { RoutingStatus.FINISHED -> { showMessage("You have arrived!") LzSdkManager.clearRoute() lzMap?.clearRoute() lzMap?.followUserLocation(false) currentRoute = null } RoutingStatus.OFF_ROUTE -> { showMessage("Recalculating...") } else -> { // Update step instructions currentRoute?.let { route -> data.currentStep?.let { step -> val steps = route.getSteps() if (step < steps.size) { updateInstructionUI(steps[step].plainInstructions) } } } } } } }

Key Design Decisions

RepoBuilder as a field: RepoBuilder is declared as a class-level property so that getRoutingStatusRepoReference() can be called from multiple methods (onMapReady, navigateTo) after the repository is built once.

withTimeoutOrNull(5000L): Indoor beacon scanning takes time. The 5-second timeout balances responsiveness with the need to wait for a first fix. If the timeout elapses, navigateTo returns without starting navigation, which is preferable to calculating a route from a stale or null location.

setCurrentShowingRoute before addRoute: Registering the route with the SDK manager first ensures that internal navigation state (step tracking, off-route detection) is aligned with the route displayed on the map.

lzMap?.clearRoute() on FINISHED: Removing the route geometry from the map at arrival gives the user a clean map state for their next destination selection without requiring an activity restart.

Common Pitfalls

Forgetting setLocationProvider before toggleLocationComponent: The location component requires a provider to know where to render the user dot. If you call toggleLocationComponent(true) before setting the provider, the component will activate but the dot will not appear.

Not null-checking currentRoute in handleNavigationStatus: Step instructions are read from currentRoute. If handleNavigationStatus is called before navigateTo assigns the route (for example, on a stale emission), a null check prevents an index-out-of-bounds crash.

Last updated on