Skip to content

// project

Mileway

Offline-first mileage, travel & expense tracker — one Kotlin codebase across Android, iOS, Wear OS, watchOS & Desktop.

Mileway is an original, fully-offline mileage / travel / expense tracker I designed and built end-to-end in Kotlin & Compose Multiplatform — running on Android, iOS, Wear OS, watchOS and Compose Desktop from one shared codebase, with zero backend so the whole thing is reproducible and reviewable. It's my reference implementation for the architecture I advocate at scale: strict module isolation, a real location engine, a policy/reimbursement layer and a durable submit-outbox, all over local data.

Kotlin MultiplatformCompose MultiplatformAndroidiOSWear OSwatchOSDesktopRoom (KMP)Koin
GitHub PaymentsLab (sibling KMP app) Field notes on this work 46 modules · 5 platforms · 159 tests

// guided tour

Two minutes, narrated

A storyboarded walkthrough of the real app — tap the speaker for the voiceover, or read along with the captions.

Mileway — narrated product tour

0

Gradle modules (36 local + 10 composed)

0

isolated feature modules

0

platforms · one codebase

0

backend calls

// multiplatform

One codebase, every surface

The real screens (and, where it's live, the running build) per platform — not a mockup.

Android screen

// in motion

Watch it run

Home & dashboard — live capture

// deep dive

How it works

46-module clean architecture (36 local + 10 composed)

Thirteen feature modules that never depend on each other, meeting only at the :app composition root and wired with Koin. A shared commonMain core holds the design system, Room (KMP) + DataStore, and every check-in / hardware-event screen, with platform services behind expect/actual. Convention plugins from my own kmp-build-logic keep every module's build consistent.

Location engine

GPS is treated as a noisy signal: jitter suppression, spike detection to reject impossible fixes, a four-bucket distance accumulator, IMU (accelerometer) fusion and device-tier-adaptive sampling that trades battery against precision by hardware class. A deterministic simulated-drive source makes the whole engine unit-testable without hardware.

Policy & reimbursement engine

A reimbursement-rate engine computes a payout from configurable per-vehicle rate rules, and the approvals flow flags policy violations against those rules — the real expense-platform logic a live product needs, implemented entirely against local data rather than stubbed with a snackbar.

Durable submit-outbox

Submitting a track or voucher journals the intent locally and reconciles it deterministically, so a process kill mid-submit never loses a record or double-counts one. Repositories are written to look one implementation-swap away from a real API — the backend is deferred, not designed out.

Five targets, one snapshot model

Beyond Android and iOS phones, the same shared SurfaceSnapshot drives a Wear OS app, a watchOS SwiftUI app and a Compose Desktop window, plus Android Glance + iOS WidgetKit home-screen widgets and an iOS Live Activity / Dynamic Island for an in-progress trip. Each surface has its own design-system skinning but reads the identical shared state.

Offline AI assistant

A chat assistant grounded entirely in local Room data — trips, expenses, cards — with real chunked streaming (not a fake typing animation), persistent history with a 5-minute session-resume window, on-device speech-to-text/text-to-speech, and local usage analytics. No remote LLM, no server, same offline guarantee as the rest of the app.

Super-profile & plugin-composition platform (V24, shipped)

The newest depth wave: a single plugin registry is the app's composition mechanism — TILE / CAPABILITY / VALUE plugins resolved by layering FORCED > USER > PRESET > DEFAULT, editable live from a Master Plugin page with source chips. Four persona presets (Corporate Commuter, Super-App Consumer, Gig Driver, Minimal Guest) reshape hubs, auth flows, tracking behaviour and tunables from one account. Built on top: act-on-behalf session delegation with an app-wide "Acting as" banner, a verification centre with corporate-email/OTP + card KYC, growth surfaces (referral, coupons, scratch rewards), membership (club, subscriptions, incentives), external wallet linking via OTP, and payout identity (masked bank + editable UPI handle + QR) — shipped, with a V25→V37 series landed on top (on-device intelligence, JWT auth, closeout hardening, home cards/advances, What's New), still zero backend.

FOSS-safe distribution & quality gates

Dual gms / noGms builds (Google Play + F-Droid) with a dependency-prefix guard that fails the build if proprietary libraries leak into the FOSS flavor. 159 Roborazzi JVM screenshot tests (no emulator, no network) covering phone, watch and desktop, plus Napier logging, detekt, ktlint, Kover and CI.

// architecture

How it's built

46-module architecture — features meet only at :app

Location pipeline — GPS treated as a noisy signal

One shared snapshot → five targets

// under the hood

Tech stack

Language & UI

  • Kotlin
  • Compose Multiplatform
  • Material 3
  • SwiftUI (watchOS)

Data

  • Room (KMP)
  • DataStore
  • Coroutines + Flow
  • Durable submit-outbox

Domain

  • Location engine (jitter · spike · IMU fusion)
  • Reimbursement-rate policy engine

DI & build

  • Koin
  • kmp-build-logic convention plugins
  • AGP
  • Gradle KTS

Maps & platform

  • MapLibre (F-Droid)
  • KrossMap (Play)
  • Glance + WidgetKit widgets
  • Live Activity / Dynamic Island

Quality

  • Roborazzi (159 JVM screenshot tests)
  • detekt
  • ktlint
  • Kover
  • CI

// gallery

Screens (64)

Swipe or use the arrows · tap a screen to enlarge

See how everything connects →