What it solves
A delivery offer arrives with a countdown, and deciding whether it is worth taking means doing pay against miles against time in your head in a few seconds. Working two platforms at once makes it worse: when offers land on both together there is no way to compare them, because neither app runs in split screen and switching to one hides the other's offer. JustDrive reads each offer on the phone as it arrives and scores it, so the comparison is already done.
How it is built
Flutter draws every screen the driver opens: setup, settings, history and export. Native Kotlin does the capture, split into a plain-Kotlin core module that parses and scores offers and is tested on the JVM, and an Android module that does the reading. Both sides share one local database, which native code writes and Dart reads. The first phase is a recorder that captures real offer screens to build test fixtures, plus an importer for past earnings history; the live scoring board is the next phase.
The hard decision
It keeps every drop-off address, and never a single customer name.
The history view compares tips between individual houses, and building-level locations, about 100 metres, were rejected because they cannot tell neighbours apart. But a home address is personal information even without a name attached, so keeping addresses came with rules. Names are skipped by the parsers and replaced with [name] in any saved screen reading. Everything stays on the phone, with no cloud sync, and the database is to be encrypted with a key held in the Android Keystore. Test fixtures made from real recordings are redacted by hand before they are committed, and the one way out, a plain-text export for moving to a new phone, warns that it contains addresses.
What it taught me
Record before you build. The plan's account of how offers arrive came from a driver's memory, and the recording phase overturned parts of it: a notification that names the store but not the pay or the miles, and a platform whose capture path had to change entirely once its real behaviour was on screen. The recordings were their own lesson too, growing to about a megabyte a minute while scrolling until snapshots that differed only by a loading animation were dropped.