Android Kotlin Compose
Use this stack when the product is Android-first and native platform behavior matters more than cross-platform reach.
Good fits:
- Offline or local-first Android apps where Room is the source of truth.
- Apps that need Android permissions, SMS workflows, background work, widgets, device APIs, or Play Store distribution.
- Kenya-first or budget-device-first apps where 2-4GB RAM phones, intermittent connectivity, low storage, and Play policy matter.
- Privacy-first finance apps where local data, exports, and restricted SMS permission evidence must be designed from day one.
Weak fits:
- Marketing sites, content apps, or admin tools that can ship faster as web apps.
- Products that must launch on iOS and Android at the same time with one UI codebase.
- Teams that cannot test on physical Android devices.
- Apps whose core value depends on restricted permissions but cannot satisfy Play policy review.
Stack Shape
| Layer | Default | Why |
|---|---|---|
| Language | Kotlin | First-class Android language with null safety, data classes, coroutines, Flow, and strong IDE support |
| UI | Jetpack Compose | Android’s recommended modern native UI toolkit |
| Design system | Material 3 | Native-feeling components, theming, dark mode, dynamic color where appropriate |
| Architecture | Single activity, UI layer, data layer, optional domain layer | Matches Android architecture guidance without overbuilding v1 |
| State | ViewModel plus immutable UI state | Survives configuration changes and keeps Compose screens testable |
| Data | Room over SQLite | Compile-time SQL checks, entities, DAOs, migrations, Flow queries |
| Background work | WorkManager | Reliable persistent work across app restarts and device reboots |
| Release | AAB through Play App Signing | Smaller delivery, upload key separation, Play-native release flow |
| Quality | Unit tests, Compose tests, Room migration tests, worker tests, physical device smoke | Android bugs hide in lifecycle, storage, permissions, and devices |
Minimum Done Criteria
Do not call an Android feature done until these are true:
| Area | Proof |
|---|---|
| Build | Debug build and release build both complete |
| Unit logic | ViewModel, repository, parser, and policy logic have local tests |
| UI | Compose screen has at least one meaningful UI test or documented manual smoke path |
| Data | Room schema is exported and migrations are tested before any version bump |
| Background work | WorkManager job has worker tests and one real-device run note |
| Permissions | Denied, granted, and permanently-denied paths are exercised |
| Play policy | Restricted permissions have a declaration plan or a no-permission fallback |
| Device | Budget Android phone or constrained emulator smoke test is recorded |
| Release | AAB or release APK is built, inspected, and installed where possible |
First Pages To Read
- Decision guide
- Starter architecture
- Commands
- Quality gates
- Agent guardrails
- Modern Android architecture
- Local-first data flow
- Background work boundaries
- Permissions and Play policy
- Prepare a Play release
- Budget-device testing
- Common failures
Recommended Default
Start with one app module, one activity, Compose screens, manual dependency wiring, one Room database, repositories, screen ViewModels, and WorkManager only for work that must survive process death.
Add Hilt, modularization, remote sync, encryption, and analytics only when the app has enough complexity to justify them.
Gotcha: Android Studio’s Run button is not release evidence. It builds and installs a debug path. Always produce a release APK or AAB before claiming release readiness.