The student-facing app of the CampusEV campus live-tracking system. Students open it and watch campus electric vehicles, vans and buses move on a live map, tap a vehicle for its route and driver, and filter by type. It's a view-only companion to the driver app and backend service.
Kotlin Multiplatform + Compose Multiplatform — shared UI across Android & iOS. Currently Android-first; the iOS target is scaffolded but not yet built (needs a Mac).
- Live map of all active campus vehicles, updated continuously.
- Buttery-smooth motion — markers glide between position updates (client-side interpolation) instead of jumping, for an Uber/Ola-style feel.
- Tap a vehicle → details sheet (label, type, route, driver, speed).
- Filter by type — EV / VAN / BUS, colour-coded.
- Campus overlay — stops, blocks, gates and landmarks drawn on the map.
- Switchable map renderer — Google Maps or MapLibre + OpenStreetMap (a one-line flag).
- Polished, modern UI — translucent "glass" panels, a live indicator, clean loading / empty / error states.
| Area | Choice |
|---|---|
| Language | Kotlin (Multiplatform) |
| UI | Compose Multiplatform (Material 3), shared across Android & iOS |
| Architecture | MVVM + Repository, unidirectional data flow with StateFlow |
| DI | Manual (AppContainer) — no framework |
| Networking | Ktor client (OkHttp on Android, Darwin on iOS) |
| Serialization | kotlinx.serialization (snake_case JSON) |
| Maps | Google Maps SDK or MapLibre + OpenStreetMap (switchable) |
| Concurrency | Kotlin coroutines + Flow |
| Lifecycle/VM | AndroidX Lifecycle (multiplatform ViewModel) |
| Build | Gradle (Kotlin DSL) + version catalog |
| CI | GitHub Actions (Android build + shared unit tests) |
| Min SDK | 26 (Android 8.0) |
The map is the only platform-specific UI (behind an expect/actual); everything else —
networking, domain logic, interpolation, view models, and the rest of the UI — lives in shared
code.
shared/ the Kotlin Multiplatform module (most of the app)
commonMain/ data (Ktor + repository) · domain (models + interpolation)
· ui (theme / components / map) · di
androidMain/ Google + MapLibre map renderers, Android HTTP engine
iosMain/ map stub + iOS HTTP engine
androidApp/ thin Android host (launches the shared UI)
iosApp/ Xcode project (iOS, later)
docs/ architecture, specs, and design docs
Deeper detail: docs/ARCHITECTURE.md.
- Android Studio (latest stable) with the Android SDK.
- JDK 21 (the bundled JetBrains Runtime is fine).
- An Android device or emulator running API 26+ (a physical device is best for GPS-driven demos).
- Internet access — the app reads live data from the backend service.
- (Only if using the Google Maps renderer) a Google Maps SDK for Android API key.
- A reachable instance of the CampusEV backend (see that project's README to run it).
Clone the repo and open the project folder in Android Studio; let Gradle sync.
Sensitive values live in local.properties, which is git-ignored (see
Secrets & .gitignore). Add your Google Maps key there:
MAPS_API_KEY=your_google_maps_android_api_key- Get a key from the Google Cloud Console (Maps SDK for Android) and restrict it to this app's package name + your signing SHA-1, and to Maps SDK for Android only.
- No key? No problem — switch to the free MapLibre renderer (next step); it needs no key.
A single flag selects the renderer, in
shared/src/androidMain/kotlin/.../map/VehicleMapActual.android.kt:
private const val USE_GOOGLE_MAPS = true // false → MapLibre + OpenStreetMap ($0, no key)The app talks to the CampusEV backend. The base URL is set in the networking layer
(shared/src/commonMain/kotlin/.../data/remote/ApiRoutes.kt) — point it at your own backend
deployment. (It is intentionally not documented here; keep deployment URLs out of the public docs.)
Android
./gradlew :androidApp:assembleDebug # build the debug APK…or just hit Run on the androidApp configuration in Android Studio (device/emulator with
internet).
iOS — deferred; the target is scaffolded but requires a Mac (Xcode + Kotlin/Native) to build.
The backend may take a few seconds to respond to the first request if it has been idle; the app shows a loading state and then goes live.
./gradlew :shared:testAndroidHostTest # shared unit testsCovered: coordinate/bearing interpolation, DTO→domain mapping, and JSON (snake_case) parsing.
GitHub Actions builds the Android app and runs the shared unit tests on every push and PR
(.github/workflows/ci.yml). iOS is not built in CI (needs a macOS runner). No secrets are
required to compile — the Maps key gracefully defaults to empty when absent.
The repo's .gitignore already excludes everything that shouldn't be published:
local.properties— holds the Maps API key; never commit it..idea/,*.iml— IDE files.build/,.gradle/,.kotlin/— build output & caches.
Before pushing, sanity-check that local.properties is not staged. Keep API keys, backend
URLs, and any deployment details out of committed files and out of this README.
This project uses spec-driven development — non-trivial work starts as a written spec that's reviewed before coding.
CONTRIBUTING.md— the workflow, branching, conventions, Definition of Donedocs/specs/— feature specs (copydocs/specs/TEMPLATE.mdto start one)docs/ARCHITECTURE.md— how the app is structuredCLAUDE.md— operating manual for AI assistants
- ✅ Live vehicle map with smooth interpolation and details/filter — spec 0001
- 🔜 Campus-places overlay fetched from the backend — spec 0002
- ⬜ Student login (institutional Google sign-in)
- ⬜ ETA to stop
- ⬜ iOS runtime
TBD — add a LICENSE file before making the repository public if you intend to set usage terms.
Part of the CampusEV system: Driver app · Student app · Backend service.