Files
kvitt/android/README.md
T
jacobh2 c277ee37c2 M2b: Android skeleton, :core:model contract, :domain passing all vectors
Gradle project in android/ with every module of SPEC §6.2 (build-logic convention plugins, version catalog,
architecture check in check), :core:model types and repository interfaces of §14.2.4 and §14.5, :domain
implementing §14.5.2 and §14.5.4 (125 vector cases), scenario-driven fakes with the empty scenario, Room 3
entities and KvittDatabase v1 with exported schema, :app shell with Hilt, en/sv strings with MissingTranslation
as a lint error, Roborazzi goldens of the empty screen in light and dark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:25:19 +02:00

61 lines
3.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.
# Kvitt Android project
One Gradle project, modules as in SPEC §6.2 and §14.7. Build only through `tools/gradle-serial.sh` with
`PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH` (CLAUDE.md › Build and verify). The gate is `tools/gradle-serial.sh check`:
compile, unit tests (including every vector of `contracts/test-vectors/`), lint (`MissingTranslation` and
`ExtraTranslation` are errors), the architecture check and the screenshot tests.
## Modules
| Module | Plugin (build-logic) | Notes |
|---|---|---|
| `:core:model` | `kvitt.jvm.library` | UI contract (§14.2.4), domain types `model.money` (§14.5.1), analytics types `model.analytics` (§14.5.4) |
| `:domain` | `kvitt.jvm.library` | §14.5 functions; `VectorTest` runs every vector file, copied by `:domain:copyTestVectors` |
| `:core:design` | `kvitt.android.library` + `kvitt.android.compose` | `KvittTheme` placeholder; M6 owns the module |
| `:core:fakes` | `kvitt.android.library` | `FakeRepositories(scenario)`, `FakeScenarios.empty`; scenarios of feature X go in `fakes/<x>/` |
| `:data` | `kvitt.android.library` + KSP + Room 3 | Room entities and `KvittDatabase` v1, schema in `data/schemas/`; M5 owns the module |
| `:adapter:fcm` | `kvitt.android.library` | shell; M8 owns it |
| `:feature:*` | `kvitt.android.feature` | shells with one entry composable; M7a to M7f own them |
| `:app` | `kvitt.android.application` + compose + hilt + Roborazzi | Application, MainActivity, goldens in `app/src/test/screenshots/` |
## Architecture check
`architectureCheck` runs in every module's `check` (`build-logic/.../ArchitectureRules.kt` holds the rules as data).
It fails on a project dependency outside the §6.2 table, on `:core:fakes` in any configuration that is not a test or
debug configuration, and, in `:core:model` and `:domain`, on an Android plugin, on a library outside
`org.jetbrains.kotlin*`, and on any import or fully qualified reference outside `kotlin.*`, `kotlinx.*` and
`se.hagstedt.kvitt.*` in `src/main`. A new module must be added to `ArchitectureRules.allowed`.
## Feature convention
* Each feature module exposes **one composable entry point per screen**. By default it obtains its ViewModel with
`hiltViewModel()` (from `androidx.hilt:hilt-lifecycle-viewmodel-compose`) and takes navigation as plain lambdas,
for example:
```kotlin
@Composable
fun AccountDetailScreen(
onOpenExpense: (ExpenseId) -> Unit,
onAddExpense: (AccountId) -> Unit,
onBack: () -> Unit,
modifier: Modifier = Modifier,
viewModel: AccountDetailViewModel = hiltViewModel(),
)
```
A stateless overload that takes the `*State` and event lambdas is what previews and screenshot tests render.
* ViewModels are `@HiltViewModel` classes that receive repositories **through the interfaces of `:core:model`** only.
`:app` binds those interfaces to `:data` (release) or `:core:fakes` (debug and tests) with Hilt (M8).
* Features never reference another feature, the `NavHost` or `:data`/`:domain`. `:app` owns the `NavHost` and wires
the lambdas.
* Strings live in the feature's own `src/main/res/values/strings.xml` and `values-sv/strings.xml`, keys prefixed with
the feature name; shared strings in `:core:design`; `:app` holds only the app name.
* Previews and screenshot tests use `FakeRepositories(scenario)` from `:core:fakes` (`debugImplementation` and
`testImplementation`, added by the feature plugin). Fakes never reach a release build.
## Screenshot tests
Roborazzi on Robolectric. `roborazzi.test.verify=true` in `gradle.properties` makes every unit-test run compare with the
committed goldens. Re-record an intended change with `tools/gradle-serial.sh recordRoborazziDebug` and open every
changed PNG before committing.