A native Android portfolio app that demonstrates a safe transfer journey using a synthetic bank. Kotlin, Jetpack Compose, MVVM and Room; three modules with explicit data ownership.
The central scenario is a timeout after the gateway accepts a transfer. The app keeps the original request, recovers its outcome after recreation, and replays safely without a second debit.
Start with the two-minute reviewer walkthrough, then inspect the execution evidence.
The successful retry, single history entry, dark theme, and 200% text are emulator captures, not mockups.
Use Android Studio with Java 17, Android SDK platform 37.0 and build tools 36.0.0. Create an ignored local.properties containing your sdk.dir, then:
./gradlew :app:assembleDebug
./gradlew allJvmTests lintDebug
./gradlew :app:connectedDebugAndroidTestThe default demo works without a server, credentials or network connection. Debug controls select success, processing, rejection, timeout after acceptance and slow response, or simulate offline. Simulated offline blocks a fresh send; an already dispatched transfer retains its uncertain outcome.
| Module | Responsibility |
|---|---|
| core:domain | Exact EUR money, Dutch IBAN validation, immutable requests, transfer states and ports |
| core:data | Separate durable client journal/cache and synthetic gateway ledger; HTTP adapter |
| app | Lifecycle-aware ViewModel, Compose transfer journey and application wiring |
The client persists before transport. Payer-scoped idempotency keys bind to an immutable payload. A pending or uncertain request prevents a replacement. Gateway balance updates and operation acceptance are atomic; transaction history deduplicates by operation ID.
sequenceDiagram
participant UI as Compose / ViewModel
participant Client as Client Room journal
participant Gateway as Synthetic gateway Room ledger
UI->>Client: Persist immutable request and key
Client->>Gateway: Submit original request
Gateway->>Gateway: Accept and debit atomically
Gateway--xClient: Response lost
Client-->>UI: Outcome unknown; retain request
Note over UI,Client: App can close and reopen here
UI->>Client: Retry safely
Client->>Gateway: Look up original key
Gateway-->>Client: Original successful operation
Client-->>UI: One operation, one history entry
Read the ownership decision, HTTP contract, and verification evidence. Repository, gateway and UI tests sit beside their owning modules. The minified release compiles without the debug scenario controls.
Katty supplied a verified dependency matrix and lessons from repairing cache ownership, migration, lifecycle restoration and retries. TransferFlow uses three modules and explicit constructor injection to keep the banking failure modes easy to inspect. Its payment submission has its own idempotency rules.
All account and recipient fixtures are synthetic. No real payments, bank login or personal customer data are used. The two local databases demonstrate ownership and recovery; they do not provide the security isolation of a remote bank. Clearing app data removes both. No production security certification or measured performance claim is made.


