Android project skeleton and Gradle setup #76

Merged
robert merged 1 commit from area/android-companion-skeleton into main 2026-09-03 23:30:58 +02:00
Owner

What

The Android companion project skeleton: settings.gradle.kts at the repository root, companion
as a first-level Gradle project with the seven feature modules nested under it
(:companion:core, :companion:pebble, :companion:location, :companion:sensors,
:companion:route, :companion:map, :companion:ride), each building, plus the JVM unit test
source set on :companion:core.

Acceptance criteria (issue #14)

  • companion/ Gradle project builds a debug APK — [VERIFIED], built for real in this
    session (./gradlew :companion:assembleDebug, 31 MB companion-debug.apk); see "How I verified
    this" below for why that's notable.
  • Kotlin, minSdk covering the target phone, modern AGP — Kotlin 2.4.10, AGP 9.4.0, minSdk 31
    (Android 12, NFR-C2 / D19). Confirmed against the built APK with aapt2 dump badging:
    minSdkVersion:'31' targetSdkVersion:'37' compileSdkVersion='37'.
  • Module structure matches the plan: pebble/, location/, sensors/, route/, map/,
    ride/ — all present under companion/, all building, dependency graph documented in
    companion/README.md.
  • PebbleKit dependency declared — io.rebble.pebblekit2:client:1.3.0 in
    companion/pebble/build.gradle.kts, and it actually resolves and compiles (not just declared on
    paper).
  • JVM unit test source set wired up and running — [VERIFIED], ran for real
    (./gradlew :companion:core:test): 1 Kotest spec, 1 pass, JUnit Platform XML report at
    companion/core/build/test-results/test/TEST-de.butzei.pedalpebble.core.CoreModuleSpec.xml.

Why this shape (docs cross-checked first, per D48)

  • docs/PROCESS.md §8: #14 is a true critical-path root (gates 51, unblocks #67 CI and #24 GPX
    import) — confirmed before starting, not assumed.
  • docs/TESTING.md §2.2/§3.1: already assumes a Gradle path literally called :companion:core
    and describes it as the module the fast lane tests because it has no Android SDK to load. That
    is only a valid Gradle path if the repo root is the Gradle root and companion is a first-level
    project — so that's the layout used here, rather than putting settings.gradle.kts inside
    companion/ (which would make the path just :core). companion/core is kotlin("jvm"), no
    Android plugin, on purpose.
  • D49 (PebbleKit Android 2 confirmed as primary transport): re-verified independently rather
    than taken on faith — see below. companion/pebble/ exists and declares the dependency so #4
    (Spike A, still open) has a module to land in, without the rest of the app depending on the
    transport question being settled.
  • D36/D50: companion/pebble/'s AndroidManifest.xml deliberately declares nothing yet —
    the receiver/queries wiring is #4's job once it's actually exercising the transport.

What I verified vs. recalled vs. reasoned (docs/TEAM.md tagging)

[VERIFIED] today, against live sources, not carried over from docs:

  • io.rebble.pebblekit2:client:1.3.0 — HTTP 200 on
    repo1.maven.org/maven2/io/rebble/pebblekit2/client/1.3.0/client-1.3.0.pom, packaging aar,
    Apache-2.0. This confirms D49 rather than reopening it (D48) — no contradiction found, just
    independent re-checking since D49 is load-bearing for this PR's module shape.
  • AGP 9.4.0 is current stable (Sept 2026 release notes; Gradle ≥9.6.0, JDK ≥17 required) and AGP 9's
    built-in Kotlin support means no org.jetbrains.kotlin.android plugin is applied in any Android
    module here.
  • Gradle 9.7.1, Kotlin 2.4.10 (both current stable per their own release feeds).
  • Kotest 6.2.4, MockK 1.14.11, Kover 0.9.9 — checked against Maven Central maven-metadata.xml
    directly (the search index lagged and under-reported). Kover matches docs/TESTING.md exactly;
    Kotest has moved from the 6.1.9 TESTING.md recorded on 2026-08-13 to 6.2.4 since — expected drift,
    not a document error, docs/TESTING.md not touched.
  • compileSdk had to become 37, not 36: found out the hard way — checkDebugAarMetadata failed
    a real build, naming Compose 1.12.0 as requiring compileSdk ≥37. Verified platforms;android-37.0
    is a real, stable (non-preview) package in Google's own SDK repository XML before relying on it.
  • androidx versions (core-ktx 1.19.0, activity-compose 1.13.0, lifecycle-runtime-ktx 2.11.0,
    compose-bom 2026.08.00) — last non-alpha/beta/rc entries in Google's Maven metadata, checked
    individually because the metadata's own <release> tag turned out to point at alphas for several
    of these artifacts.

[REASONED]:

  • Package/namespace de.butzei.pedalpebble — no existing convention in the docs, chosen to match
    the git host; trivially renameable, doesn't block anything downstream.
  • Module dependency graph (ride depends on everything, core has zero Android deps, etc.) — no
    issue specifies this beyond naming the six module folders; the graph follows from what each
    module's one-line description already implies.
  • Compose (not the classic View system) for MainActivity — not mandated anywhere I could find
    directly, but docs/TESTING.md's coverage-exclusion table already says "Compose/Activity/Fragment"
    in that order, and PebbleKit 2's own client-ui module offers a Composable permission dialog
    (relevant to D51's NFR-S8 requirement) — so Compose is already the path of least resistance for
    whoever builds the permission-rationale UI next.

How I verified this (worth knowing for #67 CI)

This sandbox has no Android Studio and, it turns out, no full JDK on PATH — the system
java is a JRE only (no javac). I found a usable JDK 21 via Rider's bundled JBR, but that build
also has no jlink, which AGP needs to build a JDK image for desugaring. I ended up downloading
a standalone Temurin JDK 21 to get a real, non-faked green build. None of this is a repo change —
it's flagged here because whatever CI image #67 uses needs a real JDK 21+, not just a java
binary on PATH
, and that's exactly the kind of thing that's cheap to catch now and annoying to
discover from a red CI run later.

Android SDK components installed to get here: platforms;android-37.0, build-tools;37.0.0,
platform-tools. Full command sequence is in the session log if useful for the CI image
definition.

Setup notes for Robert

companion/README.md has a Gradle-for-.NET-developers cheat sheet and the module graph. The one
thing worth reading even if you skim the rest: AGP 9 (this repo uses 9.4.0) removed the need for
the org.jetbrains.kotlin.android plugin — if a tutorial you're reading starts a module's
build.gradle.kts with that line, it's now not just extra but wrong to add alongside AGP's
built-in Kotlin support.

Not in this PR

No permissions declared anywhere (background location, Bluetooth, notifications) — each arrives
with the issue implementing the feature it serves, with a rationale string in values/ +
values-de/strings.xml from day one. No transport code — that's #4. No route/sensor/map logic —
those are their own issues; the modules exist as the place for that logic to land.

https://claude.ai/code/session_01DAoXbRmJUf2uxNYBfdAXPt

## What The Android companion project skeleton: `settings.gradle.kts` at the repository root, `companion` as a first-level Gradle project with the seven feature modules nested under it (`:companion:core`, `:companion:pebble`, `:companion:location`, `:companion:sensors`, `:companion:route`, `:companion:map`, `:companion:ride`), each building, plus the JVM unit test source set on `:companion:core`. ## Acceptance criteria (issue #14) - [x] `companion/` Gradle project builds a debug APK — **[VERIFIED]**, built for real in this session (`./gradlew :companion:assembleDebug`, 31 MB `companion-debug.apk`); see "How I verified this" below for why that's notable. - [x] Kotlin, minSdk covering the target phone, modern AGP — Kotlin 2.4.10, AGP 9.4.0, minSdk 31 (Android 12, NFR-C2 / D19). Confirmed against the built APK with `aapt2 dump badging`: `minSdkVersion:'31' targetSdkVersion:'37' compileSdkVersion='37'`. - [x] Module structure matches the plan: `pebble/`, `location/`, `sensors/`, `route/`, `map/`, `ride/` — all present under `companion/`, all building, dependency graph documented in `companion/README.md`. - [x] PebbleKit dependency declared — `io.rebble.pebblekit2:client:1.3.0` in `companion/pebble/build.gradle.kts`, and it actually resolves and compiles (not just declared on paper). - [x] JVM unit test source set wired up and running — **[VERIFIED]**, ran for real (`./gradlew :companion:core:test`): 1 Kotest spec, 1 pass, JUnit Platform XML report at `companion/core/build/test-results/test/TEST-de.butzei.pedalpebble.core.CoreModuleSpec.xml`. ## Why this shape (docs cross-checked first, per D48) - **`docs/PROCESS.md` §8**: #14 is a true critical-path root (gates 51, unblocks #67 CI and #24 GPX import) — confirmed before starting, not assumed. - **`docs/TESTING.md` §2.2/§3.1**: already assumes a Gradle path literally called `:companion:core` and describes it as the module the fast lane tests *because* it has no Android SDK to load. That is only a valid Gradle path if the repo root is the Gradle root and `companion` is a first-level project — so that's the layout used here, rather than putting `settings.gradle.kts` inside `companion/` (which would make the path just `:core`). `companion/core` is `kotlin("jvm")`, no Android plugin, on purpose. - **D49** (PebbleKit Android 2 confirmed as primary transport): re-verified independently rather than taken on faith — see below. `companion/pebble/` exists and declares the dependency so #4 (Spike A, still open) has a module to land in, without the rest of the app depending on the transport question being settled. - **D36/D50**: `companion/pebble/`'s `AndroidManifest.xml` deliberately declares nothing yet — the receiver/queries wiring is #4's job once it's actually exercising the transport. ## What I verified vs. recalled vs. reasoned (docs/TEAM.md tagging) **[VERIFIED] today, against live sources, not carried over from docs:** - `io.rebble.pebblekit2:client:1.3.0` — HTTP 200 on `repo1.maven.org/maven2/io/rebble/pebblekit2/client/1.3.0/client-1.3.0.pom`, packaging `aar`, Apache-2.0. This *confirms* D49 rather than reopening it (D48) — no contradiction found, just independent re-checking since D49 is load-bearing for this PR's module shape. - AGP 9.4.0 is current stable (Sept 2026 release notes; Gradle ≥9.6.0, JDK ≥17 required) and AGP 9's built-in Kotlin support means no `org.jetbrains.kotlin.android` plugin is applied in any Android module here. - Gradle 9.7.1, Kotlin 2.4.10 (both current stable per their own release feeds). - Kotest 6.2.4, MockK 1.14.11, Kover 0.9.9 — checked against Maven Central `maven-metadata.xml` directly (the search index lagged and under-reported). Kover matches `docs/TESTING.md` exactly; Kotest has moved from the 6.1.9 TESTING.md recorded on 2026-08-13 to 6.2.4 since — expected drift, not a document error, `docs/TESTING.md` not touched. - **compileSdk had to become 37, not 36**: found out the hard way — `checkDebugAarMetadata` failed a real build, naming Compose 1.12.0 as requiring compileSdk ≥37. Verified `platforms;android-37.0` is a real, stable (non-preview) package in Google's own SDK repository XML before relying on it. - androidx versions (`core-ktx` 1.19.0, `activity-compose` 1.13.0, `lifecycle-runtime-ktx` 2.11.0, `compose-bom` 2026.08.00) — last non-alpha/beta/rc entries in Google's Maven metadata, checked individually because the metadata's own `<release>` tag turned out to point at alphas for several of these artifacts. **[REASONED]:** - Package/namespace `de.butzei.pedalpebble` — no existing convention in the docs, chosen to match the git host; trivially renameable, doesn't block anything downstream. - Module dependency graph (`ride` depends on everything, `core` has zero Android deps, etc.) — no issue specifies this beyond naming the six module folders; the graph follows from what each module's one-line description already implies. - Compose (not the classic View system) for `MainActivity` — not mandated anywhere I could find directly, but `docs/TESTING.md`'s coverage-exclusion table already says "Compose/Activity/Fragment" in that order, and PebbleKit 2's own `client-ui` module offers a Composable permission dialog (relevant to D51's NFR-S8 requirement) — so Compose is already the path of least resistance for whoever builds the permission-rationale UI next. ## How I verified this (worth knowing for #67 CI) This sandbox has no Android Studio and, it turns out, **no full JDK on `PATH`** — the system `java` is a JRE only (no `javac`). I found a usable JDK 21 via Rider's bundled JBR, but that build *also* has no `jlink`, which AGP needs to build a JDK image for desugaring. I ended up downloading a standalone Temurin JDK 21 to get a real, non-faked green build. None of this is a repo change — it's flagged here because **whatever CI image #67 uses needs a real JDK 21+, not just a `java` binary on `PATH`**, and that's exactly the kind of thing that's cheap to catch now and annoying to discover from a red CI run later. Android SDK components installed to get here: `platforms;android-37.0`, `build-tools;37.0.0`, `platform-tools`. Full command sequence is in the session log if useful for the CI image definition. ## Setup notes for Robert `companion/README.md` has a Gradle-for-.NET-developers cheat sheet and the module graph. The one thing worth reading even if you skim the rest: AGP 9 (this repo uses 9.4.0) removed the need for the `org.jetbrains.kotlin.android` plugin — if a tutorial you're reading starts a module's `build.gradle.kts` with that line, it's now not just extra but wrong to add alongside AGP's built-in Kotlin support. ## Not in this PR No permissions declared anywhere (background location, Bluetooth, notifications) — each arrives with the issue implementing the feature it serves, with a rationale string in `values/` + `values-de/strings.xml` from day one. No transport code — that's #4. No route/sensor/map logic — those are their own issues; the modules exist as the place for that logic to land. https://claude.ai/code/session_01DAoXbRmJUf2uxNYBfdAXPt
#14 gates 51 downstream issues (docs/PROCESS.md §8) and unblocks #67 (CI needs a Gradle project
to build) and #24 (GPX import needs somewhere to live). Shaping the module graph now, even before
#4 (Spike A) has landed, means the transport question stays isolated to one module instead of
threading through the app as it's retrofitted later.

Repository root is now the Gradle root (settings.gradle.kts), with `companion` as a first-level
project and the seven feature modules nested under it — matching the `:companion:core` Gradle path
docs/TESTING.md's coverage policy already assumed, rather than inventing a different layout.
`:companion:core` is pure JVM with no Android dependency on purpose: it's the module the fast lane
tests on every push specifically because it has no SDK to load (docs/TESTING.md §2.2/§3.1).

compileSdk ended up at 37 rather than 36, not by preference but because Compose 1.12.0 (current
today) refuses to compile against 36 and says so in the AAR metadata check — checked against a real
build, not assumed. AGP 9.4.0, Kotlin 2.4.10, Gradle 9.7.1 and the docs/TESTING.md-listed Kotest/
MockK/Kover versions were all confirmed against Maven Central / Google's Maven today; PebbleKit
Android 2 (io.rebble.pebblekit2:client:1.3.0) was independently re-verified on Maven Central,
confirming D49 rather than reopening it (D48).

No permissions are declared anywhere yet — each arrives with the issue that implements the feature
it serves, with a rationale string from day one (values/ + values-de/ already wired up).

Closes #14
Claude-Session: https://claude.ai/code/session_01DAoXbRmJUf2uxNYBfdAXPt
robert merged commit c729ce2c5e into main 2026-09-03 23:30:58 +02:00
Sign in to join this conversation.
No description provided.