Files
samplez/rippr-src/README.md
2026-08-11 12:09:35 -05:00

119 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Rippr
A native Android GPS ride recorder for motorcycles. Records telemetry from an
un-killable foreground service into a local SQLite database, renders the traversed path
on a map afterwards, and exports to GPX/GeoJSON.
Built for one specific use: **hit start, put the phone in a pocket, ride, hit stop.**
Every design decision below follows from that.
```
Package com.rippr minSdk 26 (Android 8.0) targetSdk 35
Kotlin 2.0.21 AGP 8.7.3 Gradle 8.11.1
~5,800 lines Kotlin 84 unit tests 46 instrumented tests
```
## Current state — v2.0.1
| Feature | Status |
|---|---|
| GPS recording via foreground service | Working, validated on real rides |
| Trips with pause/resume/stop/discard | Working |
| Live speed + elapsed clock | Working (fixed in 2.0.1) |
| Path rendered on OpenStreetMap | Working (fixed in 2.0.1) |
| Ride statistics + charts | Working; elevation gain has known drift |
| Rename / delete / merge rides | Working |
| GPX + GeoJSON export | Working, validated against an external parser |
| Upload to a REST endpoint | Implemented, **no UI** — see below |
| Live map during recording | Absent in v2; planned for v3 |
| Group ride view | Deferred to v3 |
## Quick start
Requires **JDK 17–21** (AGP does not support 25) and the Android SDK with platform 35 and
build-tools 35. Full setup in [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md).
```bash
echo "sdk.dir=$HOME/Library/Android/sdk" > local.properties
./gradlew assembleDebug testDebugUnitTest lintDebug
./gradlew connectedDebugAndroidTest # needs a device or emulator
```
## Architecture in one page
```
TrackingService (foreground)
└─ FusedLocation callback ──trySend──► unbounded Channel
│
single writer coroutine (batches of 25, ~2s)
│
┌───────────────────┴────────────────────┐
▼ ▼
Room database Accumulator
Trip → Segment → TrackPoint distance / moving time /
│ elevation, folded per batch
▼
TripRepository (all lifecycle transitions)
│
┌─────────────────┼──────────────────┐
▼ ▼ ▼
RecordScreen TripsScreen TripDetailScreen
└─ RideMap (osmdroid)
```
Three decisions carry most of the weight:
**The location callback never blocks.** Fixes go into an unbounded `Channel`; a single
writer coroutine drains it in batches. Slow disk stalls the writer, never GPS, and no fix
is dropped under back pressure.
**Recording state lives in the database, not memory.** A row in `trips` with
`endedAt IS NULL` *is* the fact of an in-progress ride. It survives process death, which
an in-memory flag cannot — `START_STICKY` restarts the service with a null intent and a
flag would come back `false` mid-ride.
**Segments make pause correct rather than cosmetic.** Without them, pausing at a gas
station and resuming across town draws a straight line through terrain never ridden, and
counts it as distance. Segments propagate all the way through: distance accumulation, map
polylines, and GPX `<trkseg>`.
Full reasoning in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
## Deliberate absences
**No live map while recording** — *superseded, see [docs/v3/BACKLOG.md](docs/v3/BACKLOG.md).*
v2 shipped without one because the phone rides in a pocket, so a live map would burn battery
for something nobody is looking at. That premise no longer holds: handlebar mounting is now
a wanted use case. The constraint that survives is architectural — `TrackingService` holds
no reference to any map type, and rendering belongs to a visible screen's Compose lifecycle.
**No UI for the upload endpoint.** The REST uploader works and is tested, but is only
reachable via `Config.setUploadEndpoint()`. It has no server to talk to yet; the UI arrives
when group-ride streaming does.
## Documentation
| Document | Contents |
|---|---|
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Durable design decisions and the reasoning behind them |
| [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) | Toolchain setup, build/test commands, emulator harness |
| [docs/TESTING.md](docs/TESTING.md) | What is covered, what is not, and what the emulator cannot verify |
| [docs/v1/](docs/v1/) | Original spec, what shipped, and how it deviated |
| [docs/v2/](docs/v2/) | 18-task plan, per-task docs, and the progress log with every bug found |
| [docs/v3/BACKLOG.md](docs/v3/BACKLOG.md) | Known gaps, deferred work, and ideas for next |
**Start here for v3:** [docs/v3/BACKLOG.md](docs/v3/BACKLOG.md), then
[docs/v2/PROGRESS.md](docs/v2/PROGRESS.md) for the bugs that were found and why.
## Known gaps
1. **Elevation gain drifts** ~30 m per ten stationary minutes against synthetic noise.
Real GPS error is correlated rather than uniform, so the true figure needs a real ride.
Flat ground should read near zero.
2. **No Compose UI tests** for any of the six screens. Verification was manual.
3. **Speed colouring on the map is unvalidated** — the emulator reports zero velocity, so
the path renders uniformly there.
4. **Migrations are now mandatory.** `fallbackToDestructiveMigration()` was removed in v2;
any schema change must ship a `Migration` against
`app/schemas/com.rippr.data.AppDatabase/2.json`.