Files
navigation/CLAUDE.md
T

14 KiB

CLAUDE.md

This file provides guidance to Claude Code when working with code in this repository.

Project Overview

This is an Android navigation app built with Jetpack Compose that supports multiple routing providers (OSRM, Valhalla, TomTom) and includes Android Auto/Automotive OS integration. The app uses MapLibre for rendering, Androidx DataStore for local persistence, and Koin for dependency injection.

Build Commands

# Build the app (from repository root)
./gradlew :app:assembleDebug

# Build a specific flavor
./gradlew :app:assemblePlayDebug
./gradlew :app:assembleDemoDebug
./gradlew :app:assembleFullDebug

# Run unit tests
./gradlew test

# Run tests for a specific module
./gradlew :common:data:test
./gradlew :common:car:test

# Install on device
./gradlew :app:installDebug

# Clean build
./gradlew clean

Module Structure

The project uses a multi-module architecture (see settings.gradle.kts):

  • app/ - Main Android app with Jetpack Compose UI for phone (com.kouros.navigation)
  • common/data/ - Core data layer with routing logic, repositories, view models, persistence (com.kouros.data)
  • common/car/ - Android Auto/Automotive OS UI implementation
  • automotive/ - Placeholder for future native Automotive OS app (no Kotlin sources yet)

Dependencies flow: appcommon:carcommon:data

Architecture

Routing Providers (Pluggable System)

The app supports three routing engines that extend the NavigationRepository abstract class (common/data/.../data/NavigationRepository.kt):

  1. ValhallaRepository - Valhalla routing engine (ordinal 0)
  2. OsrmRepository - OSRM routing engine (ordinal 1)
  3. TomTomRepository - TomTom routing engine (ordinal 2, default)

Selection is driven by RouteEngine enum order (see Data.kt). NavigationRepository also exposes shared HTTP helpers (fetchUrl, searchPlaces, reverseAddress) that use Nominatim for geocoding and ApplicationConfig-backed HTTP basic auth for protected endpoints.

Each provider has a corresponding mapper (OsrmRoute, ValhallaRoute, TomTomRoute) that converts provider-specific JSON into the universal Route model. The universal model is decomposed into a dedicated data/route/ package: Routes, Leg, Step, Maneuver, Intersection, Lane, Summary.

Adding a new routing provider:

  1. Create NewProviderRepository extending NavigationRepository under common/data/src/main/java/com/kouros/navigation/data/<provider>/
  2. Implement getRoute() and getTraffic()
  3. Create NewProviderRoute.kt with a mapToRoute(response, builder) function that populates a Route.Builder
  4. Add a RouteEngine enum entry and provider branch in Route.Builder.route() (data/Route.kt)
  5. Update NavigationUtils.getViewModel() (utils/NavigationUtils.kt) to return a NavigationViewModel wired to the new repository

Data Flow

User action (search / select destination)
    ↓
NavigationViewModel.loadRoute() [LiveData / Flow]
    ↓
NavigationRepository.getRoute() [selected provider]
    ↓
*Route.mapToRoute() [convert to universal Route]
    ↓
RouteModel.startNavigation()
    ↓
RouteCalculator.findStep() [on each location update]
    ↓
NavigationState updated → UI observes and renders current step

Key Classes

Navigation logic (common/data/.../model/):

  • RouteModel.kt - Core navigation engine; tracks position, manages step progression, owns NavigationState
  • RouteCalculator.kt - Step-finding algorithm: snaps current location to the nearest waypoint, computes leftover distance, handles snap correction and reroute thresholds
  • RouteCarModel.kt (common/car/.../navigation/) - Extends RouteModel with Android Auto-specific formatting
  • NavigationViewModel.kt - androidx ViewModel exposing route, traffic, places (Nominatim), amenities (Overpass), and fuel prices (Tankerkönig) as LiveData
  • SettingsViewModel.kt - State holder for DataStore-backed settings (dark mode, 3D, routing engine, avoid preferences, etc.)
  • BaseStyleModel.kt - Map style state

Data models (common/data/.../data/):

  • Route.kt - Universal route wrapper with Route.Builder and provider dispatch
  • data/route/* - Decomposed route components (Routes, Leg, Step, Maneuver, Intersection, Lane, Summary)
  • NavigationState.kt - Immutable navigation state (route, flags, location, bearing, maneuver, destination)
  • Data.kt - Shared types (Place, StepData, SearchFilter, Locations, ValhallaLocation) plus object Constants and the RouteEngine, DarkMode, EngineType, ViewStyle, NavigationThemeColor enums
  • ApplicationConfig.kt - Loads USER / PASSWORD from BuildConfig for HTTP basic auth on protected endpoints

Persistence (common/data/.../data/datastore/ and common/data/.../repository/):

  • DataStoreManager.kt - Single source of truth for preference keys; exposes Flow<T> reads and suspend writes for each setting
  • SettingsRepository.kt - Higher-level wrapper over DataStoreManager

Repositories (common/data/.../data/):

  • NavigationRepository.kt - Abstract base class for routing providers; also handles Nominatim geocoding (search and reverse)
  • osrm/, valhalla/, tomtom/ - Provider implementations and JSON DTOs
  • fuel/FuelPrices.kt - Tankerkönig fuel-price client (returns List<Station>)
  • overpass/ - Overpass API client for POIs and speed limits

Android Auto / Automotive (common/car/):

  • NavigationCarAppService.kt - CarAppService entry point
  • CarSession.kt (abstract) and concrete NavigationSession.kt, ClusterSession.kt
  • screen/NavigationScreen.kt - Main navigation template; NavigationType enum drives template selection (VIEW, NAVIGATION, REROUTE, RECENT, ARRIVAL)
  • screen/SearchScreen.kt, RoutePreviewScreen.kt, PlaceListScreen.kt, CategoriesScreen.kt, CategoryScreen.kt, StopOverScreen.kt, RequestPermissionScreen.kt
  • screen/settings/* - Per-setting screens (RoutingSettings, NavigationSettings, DisplaySettings, DarkModeSettings, DistanceSettings, AudioSettings, CarSettings, PasswordSettings)
  • screen/observers/* - Observer pattern that bridges LiveData to car screens. NavigationObserverManager orchestrates RouteObserver, TrafficObserver, TrafficMessageObserver, PlaceSearchObserver, CategoryObserver, MaxSpeedObserver, SpeedCameraObserver
  • Supporting managers: SurfaceRenderer.kt (virtual display + map), DeviceLocationManager.kt, CarSensorManager.kt, NavigationNotificationManager.kt + NavigationNotificationService.kt, TextToSpeechManager.kt, CustomLifecycleOwner.kt
  • map/MapView.kt, map/LocationPuck.kt - MapLibre rendering for the car surface

External APIs

Service Purpose URL
OSRM Routing https://router.project-osrm.org/route/v1/driving/
Valhalla Routing https://kouros-online.de/valhalla/route?json= (HTTP basic auth)
TomTom Routing https://api.tomtom.com/routing/1/calculateRoute/
TomTom Traffic incidents https://api.tomtom.com/traffic/services/5/incidentDetails
Nominatim Geocoding (search/reverse) https://nominatim.openstreetmap.org/
Overpass POIs & speed limits OpenStreetMap Overpass API (DEBUG builds use https://kouros-online.de/api/interpreter)
Tankerkönig Fuel prices https://creativecommons.tankerkoenig.de/json/list.php? (API key in fuel/FuelPrices.kt)

In DEBUG builds, TomTomRepository and FuelPrices can be pointed at a local fixture (see useLocal flags) — TomTom can fall back to R.raw.tomom_routing, fuel falls back to a local JSON URL.

Important Constants

Defined in object Constants inside common/data/.../data/Data.kt:

NEXT_STEP_THRESHOLD = 500.0           // Distance (m) to show next maneuver
DESTINATION_ARRIVAL_DISTANCE = 10.0   // Distance (m) to trigger arrival
MAXIMAL_SNAP_CORRECTION = 50.0        // Max distance (m) to snap to route
MAXIMAL_ROUTE_DEVIATION = 100.0       // Max deviation (m) before reroute
NEAREST_LOCATION_DISTANCE = 10F
SPEED_UPDATE_DISTANCE = 600F
INSTRUCTION_DISTANCE = 50
SPEED_BEARING_DEVIATION = 60
TILT = 60.0                           // Map tilt in degrees during navigation
TANKER_KOENIG_DELAY = 300_000         // ms between fuel price refreshes

DataStore keys (see DataStoreManager.PreferencesKeys):

  • RoutingEngine (Int) — 0=Valhalla, 1=OSRM, 2=TomTom (default 2)
  • DarkMode (Int), Show3D (Bool), CarLocation (Bool)
  • AvoidMotorway, AvoidTollway, AvoidFerry (Bool)
  • LastRoute, RecentPlaces, FuelPrices, LastFuelPrices
  • TomTomApiKey, DistanceMode, GuidanceAudio, Traffic, TripSuggestion, EngineType, AlternativeRoutes

Navigation Flow

  1. Route loading — User searches via Nominatim → selects place → NavigationViewModel.loadRoute() calls the selected repository
  2. Route parsing — Provider JSON → *Route.mapToRoute() populates Route.Builder → universal RouteRouteModel.startNavigation()
  3. Location trackingFusedLocationProviderClient updates → RouteModel.updateLocation()
  4. Step calculationRouteCalculator.findStep() snaps location to the nearest waypoint, returns StepMatch, and updates the current step index
  5. UI updatesNavigationState flows out via LiveData/Compose state; phone and car UIs render the current/next step (instruction, distance, icon, lanes)
  6. Arrival — When distance < DESTINATION_ARRIVAL_DISTANCE, navigation ends

Testing Navigation

The phone app supports mock locations for testing:

  • Set useMock = true in MainActivity
  • Enable "Mock location app" in Android Developer Options
  • Choose mode in model/Simulation.kt / model/MockLocation.kt:
    • type = 1 — Simulate movement along the entire route
    • type = 2 — Test a specific step range
    • type = 3 — Replay a GPX track file

The car module has its own navigation/Simulation.kt for car-side simulation.

Unit tests

  • common/data/src/test/.../model/RouteCalculatorTest.kt
  • common/data/src/test/.../model/RouteModelTest.kt
  • common/data/src/test/.../model/IconMapperTest.kt
  • common/data/src/test/.../model/OverpassTest.kt
  • common/data/src/test/.../utils/GeoUtilsTest.kt
  • common/car/src/test/.../screen/NavigationScreenTest.kt
  • common/car/src/test/.../screen/observers/CategoryObserverTest.kt, ObserversTest.kt

Persistence

All app preferences are stored via Androidx DataStore Preferences (navigation_settings data store). There is no Room/ObjectBox/SQL database — the only persisted entities are settings and serialized lists (recent places, last route, last fuel prices) kept as strings.

DataStoreManager exposes a Flow<T> per setting plus a matching suspend set* writer. SettingsRepository wraps it for higher-level access. SettingsViewModel and AppViewModel consume those flows for UI state.

Compose UI Structure

Phone app (app/src/main/java/com/kouros/navigation/):

  • ui/MainActivity.kt - Entry point with permission handling and Navigation Compose host
  • ui/MapView.kt - MapLibre rendering with camera state management
  • ui/SheetLayout.kt - Bottom sheet scaffold
  • ui/PermissionScreen.kt
  • ui/navigation/AppNavGraph.kt - Compose navigation graph
  • ui/navigation/NavigationScreen.kt, NavigationSheet.kt - Turn-by-turn UI
  • ui/search/SearchScreen.kt, SearchSheet.kt
  • ui/settings/SettingsScreen.kt, SettingsRoute.kt, DisplayScreen.kt, NavigationScreen.kt, CarScreen.kt, Settings.kt
  • ui/components/SettingItem.kt, SettingSwitch.kt, RadioButtonSingleSelection.kt, SectionTitle.kt
  • ui/app/AppViewModel.kt, AppViewModelProvider.kt
  • ui/theme/{Color,Type,Shapes,Theme}.kt
  • model/Simulation.kt, model/MockLocation.kt - Mock location for testing
  • di/appModule.kt - Koin module
  • MainApplication.kt - Application class

Android Auto uses CarAppService templates (NavigationTemplate, MessageTemplate, MapWithContentTemplate). NavigationType (in screen/NavigationScreen.kt) controls which template to render. UI state is synchronized with NavigationViewModel through the observers in screen/observers/.

Build Flavors

app/build.gradle.kts defines three product flavors under the store dimension:

  • play - applicationIdSuffix .play
  • demo - applicationIdSuffix .demo
  • full - applicationIdSuffix .full

Base namespace is com.kouros.navigation; current versionName is 0.3.0.109. Java/Kotlin target is 21 for the app module, 11 for common:data. compileSdk/targetSdk = 37, minSdk = 33.

signing.properties (root, gitignored) provides the keystore credentials for both debug and release builds. local.properties provides USER / PASSWORD build config fields consumed by ApplicationConfig.

Common Patterns

Dependency injection (Koin):

single { OsrmRepository() }
viewModel { NavigationViewModel(get()) }

LiveData observation:

viewModel.route.observe(this) { routeJson ->
    routeModel.startNavigation(routeJson, context)
}

Step-finding algorithm: RouteCalculator iterates the current step's waypoints, calculates distance from the current location to each, and snaps to the nearest waypoint. Snap is rejected if the closest point exceeds MAXIMAL_SNAP_CORRECTION; deviation greater than MAXIMAL_ROUTE_DEVIATION triggers a reroute.

Settings flow:

val darkMode by viewModel.darkMode.collectAsStateWithLifecycle()

Known Limitations

  • Valhalla route mapping is incomplete in places (search for TODO comments in data/valhalla/ValhallaRoute.kt)
  • Rerouting logic exists but needs more testing
  • Speed-limit queries via Overpass API could be optimized for performance
  • TomTom and Tankerkönig clients have DEBUG-only useLocal shortcuts that hit fixture URLs (http://192.168.1.37/... and R.raw.tomom_routing) — these need to be off for any real-network testing
  • The automotive module is wired into Gradle but has no Kotlin source yet