Files
samplez/rippr-src/docs/DEVELOPMENT.md
uhryniuk 247b9cdb3f Refresh Rippr snapshot and bundle with full project documentation
Re-exported at 46a0726, which adds README.md plus docs/ARCHITECTURE,
DEVELOPMENT, TESTING, v1 history including the original brief, and a v3
backlog. 112 files, and the bundle now carries 19 commits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 08:44:48 -05:00

189 lines
6.1 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.

# Development
Everything needed to build, test, and drive Rippr without Android Studio. This was all
done from the command line; the GUI is not required at any point.
---
## Toolchain
### JDK — must be 17–21
**Not 25.** AGP 8.7 does not support it, and the failure is confusing. This machine runs
JDK 25 by default via `mise`, so builds need an explicit `JAVA_HOME`:
```bash
export JAVA_HOME=~/.local/share/mise/installs/java/temurin-21.0.12+8.0.LTS
export PATH="$JAVA_HOME/bin:$PATH"
```
Install with `mise install java@temurin-21` if it is missing.
### Android SDK
```bash
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH"
```
Needs platform 35 and build-tools 35. `sdkmanager` and `avdmanager` come from
`brew install android-commandlinetools`, but note the brew binaries resolve their own SDK
root and **will not see** `~/Library/Android/sdk` — `avdmanager create` fails with
"Package path is not valid" even when the image is installed. Hand-editing
`~/.android/avd/*.ini` is more reliable than fighting it.
### Disk space — the surprise blocker
The emulator enforces a **fixed ~7.4 GB free-space minimum** before it will boot. This is
not tunable: shrinking the AVD's `disk.dataPartition.size` and passing `-partition-size`
both leave the requirement unchanged.
A full build needs roughly 3–5 GB on top of that. Reclaimable without touching source:
```bash
brew cleanup -s
rm -rf ~/Library/Caches/Homebrew/* ~/Library/Caches/ms-playwright
pip cache purge; go clean -cache
```
---
## Build and test
```bash
./gradlew assembleDebug # debug APK
./gradlew assembleRelease # release APK (unsigned — will not install)
./gradlew testDebugUnitTest # 84 tests, no device needed
./gradlew lintDebug
./gradlew connectedDebugAndroidTest # 46 tests, needs a device
```
Only the **debug** APK installs directly; release is unsigned.
### Reading results without the HTML report
```bash
python3 -c "
import re,glob
t=f=0
for p in glob.glob('app/build/test-results/testDebugUnitTest/*.xml'):
m=re.search(r'tests=\"(\d+)\".*?failures=\"(\d+)\".*?errors=\"(\d+)\"',open(p).read())
t+=int(m.group(1)); f+=int(m.group(2))+int(m.group(3))
print(f'{t} run, {f} failed')
"
```
### Room schema export
Schemas land in `app/schemas/com.rippr.data.AppDatabase/`. **The directory name follows the
`@Database` class package** — when the class moved from `com.rippr` to `com.rippr.data`,
the export path moved with it, which initially looked like the schema had not generated at
all.
---
## Emulator
```bash
emulator -avd Medium_Phone_API_35 -no-window -no-audio -no-boot-anim \
-gpu swiftshader_indirect
adb emu kill # shut down
```
Grant permissions up front so the runtime dialog does not eat your taps:
```bash
for p in ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION POST_NOTIFICATIONS; do
adb shell pm grant com.rippr android.permission.$p
done
```
### Feeding a synthetic ride
```bash
adb emu geo fix <lon> <lat> <altitude>
```
**Coordinate order is longitude first.** See [TESTING.md](TESTING.md) for what this
cannot simulate — it matters more than it sounds.
### Driving the UI
The service is `exported=false`, so `adb shell am start-service` is **correctly refused**.
Drive through the UI, or from an instrumented test running in the app's own process.
Locating a control by its label:
```bash
tapText() {
adb shell uiautomator dump /sdcard/u.xml >/dev/null 2>&1
adb shell cat /sdcard/u.xml | python3 -c "
import sys,re
d=sys.stdin.read()
m=re.search(r'text=\"$1\"[^>]*bounds=\"\[(\d+),(\d+)\]\[(\d+),(\d+)\]\"', d)
print(f'{(int(m.group(1))+int(m.group(3)))//2} {(int(m.group(2))+int(m.group(4)))//2}' if m else 'NONE')
"
}
read x y <<< "$(tapText 'START RECORDING')"; adb shell input tap $x $y
```
### Inspecting the database
`sqlite3` is **not present** on the emulator image. Pull the files instead — and take the
`-wal` too, or recent writes are missing:
```bash
for f in rippr_db rippr_db-wal rippr_db-shm; do
adb exec-out run-as com.rippr cat databases/$f > /tmp/db/$f
done
python3 -c "
import sqlite3; c=sqlite3.connect('/tmp/db/rippr_db')
print(c.execute('SELECT id,state,ROUND(distanceM),pointCount FROM trips').fetchall())
"
```
---
## Hard-won harness lessons
These cost real time. All were false alarms that looked like app bugs.
**Never `sleep` and assume a tap landed.** A cold start took 8.7 s once; a 4-second sleep
produced a silently-missed tap and a false "the service didn't start" conclusion. Poll
`uiautomator dump` for the expected text, then tap.
**Even a confirmed-present control can swallow a tap** right after a fresh install.
`uiautomator` reported the button present, the tap returned success, and nothing happened —
no `databases/`, no service. Repeating it moments later worked.
**Verify database state between UI steps.** Every emulator false alarm in this project came
from trusting a tap instead of checking what actually happened. Checking for the *absence
of the data directory* is what finally made one of them obvious.
**Suspiciously identical results mean a broken harness.** A constant-sweep loop returned
four results identical to six decimal places. The cause was a shell quoting bug that
corrupted the source file while the compile error hid behind `/dev/null`. Never redirect a
build to `/dev/null` inside a measurement loop.
**The notification shade stays open between runs** and will cover the app, making
`uiautomator` report quick-settings tiles instead of your UI. `adb shell cmd statusbar
collapse` first.
---
## Design assets
Logos and icons are generated, not hand-drawn:
```bash
python3 design/gen_logos.py # SVG concepts + PNG previews (needs rsvg-convert)
python3 design/to_vector_drawable.py # → app/src/main/res/drawable/
python3 design/build_preview.py # review page
```
`design/material/` vendors Google Material Symbols (Apache-2.0) as reference geometry.
The launcher icon lives on Android's 108-unit adaptive canvas. **Content must stay inside
the centre 66-unit safe circle** — the first version drew its ring at r=43 and the launcher
mask cropped it clean off, taking the accent segment with it.