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.
NavigationActivity: From Venue Selection to Turn-by-Turn
The flow is divided into five sequential stages, each triggered by the completion of the previous one:
- Initialize the SDK.
- Load available venues and let the user choose.
- Build the map for the selected venue and load place markers.
- Set up location tracking with the position repository.
- 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.
Related Examples
- Route Calculation and Display - How route requests work in detail
- Indoor Navigation - Focused example of the navigation status monitoring loop
- Route Styling - Customize the route line style passed to
addRoute - Error Handling - Add robust error handling to each stage of this flow
- Tracking Config - Configure the
TrackingConfigused insetupMap