stack.menu

Kotlin + Compose

Native Android apps with platform UI, lifecycle and storage integration.

Compose · ViewModel · Room · Coroutines

Complete guide (Markdown) · Structured record (JSON)

Original source review: 2026-10-10. Core guidance rechecked: 2026-10-13. Check scope. Dependency combinations and setup need project-specific qualification.

Use cases

Choose it when

Consider another path when

Working practices

Tools by job

UI and state: Jetpack Compose + Material 3; screen ViewModels exposing immutable StateFlow with lifecycle-aware collection

Unidirectional data flow separates user events and rendered state; repositories own data access

ViewModels do not survive process death; persist important state and test restoration; add domain layers only for actual complexity

Structured local persistence: Room

Compile-time SQL checking, migrations and observable local data

Choose compatible Room/KSP/Kotlin versions; test upgrades; neither Room nor DataStore automatically supplies encryption or backend sync

Preferences: DataStore

Compile-time SQL checking, migrations and observable local data

Choose compatible Room/KSP/Kotlin versions; test upgrades; neither Room nor DataStore automatically supplies encryption or backend sync

Networking and API contract: Retrofit + OkHttp + kotlinx.serialization behind a repository

Typed HTTP interfaces with explicit DTOs and a replaceable client

Pin compatible versions and converter; share schemas/fixtures with iOS rather than UI; test auth refresh, errors and cancellation

Concurrency and background work: Coroutines/Flow in lifecycle-owned scopes; WorkManager for eligible persistent deferrable work

Cancellation follows lifecycle while scheduled local sync uses a persistent work system

WorkManager is OS-constrained and not an exact scheduler; coroutine launch alone is not durable across process death

Credentials: Android Keystore-backed cryptographic keys protecting app-private credential material

Use OS key protection and explicitly scoped encrypted storage

Keystore stores keys, not arbitrary bearer tokens; define encryption, backup exclusions and revocation; no backend provider secrets in APKs

Unit and integration tests: JUnit + coroutine test utilities; fake repositories/API; Room migration tests

Deterministically verify state, retries, decoding, migrations and process-restoration assumptions

Host unit tests cannot prove Android framework/device behavior; emulator tests supplement them

UI and accessibility: Compose UI tests + AndroidX accessibility checks; TalkBack and large-font/device review

Verify semantic actions, navigation and permission flows with the actual platform UI

Automatic checks require supported test dependencies/API environment and do not replace screen-reader usability; test tablets/foldables if supported

Build, CI and distribution: Android Studio + Gradle wrapper/Kotlin DSL; pinned JDK/SDK CI; signed Android App Bundle and Play internal testing

Reproduce build inputs and separate local tests, device tests and signed release artifacts

AGP/Gradle/JDK/Kotlin/Compose must be compatible; release requires upload-key custody and Play account access; build success is not store publication

Navigation: Navigation Compose

Navigation host/controller handle screen back stacks and deep links

Add compatible navigation-compose dependency; pass minimal route identifiers and test process recreation/back behavior. Preserve Fragment navigation during partial Compose migration.

Notifications: Android notification channels + runtime permission

Native notifications keep user delivery preferences under system control

Declare/request POST_NOTIFICATIONS where required; create appropriate channels, handle denial and test release API levels. Remote transport/server enrollment is a separate integration.

Media selection: Android Photo Picker

System picker grants access to selected media

Use Activity result contracts; handle fallback devices, URI access lifetime and upload size limits. Prefer selected access over requesting broad media-library permissions.

Build with the stack’s strengths

Architecture and developer workflow.

An installed Android client renders immutable screen state with lifecycle-aware collection and persists important data separately. Recommendation: repositories mediate Room/API data and WorkManager handles selected persistent work; backend services own privileged domain effects. Installed versions coexist through staged distribution.

Version scope: Documentation reviewed 11 October 2026. Use the project's minimum/target SDK, compatible Kotlin/Compose/AGP/Gradle/JDK/Room/WorkManager versions and version catalog. Current guidance is conditional on supported platform behavior; no APK, App Bundle, emulator or store release was tested. · Reviewed 2026-10-11

Architecture boundaries

Tools and feedback

Translations to avoid

For an existing application

Keep the contracts while you change the stack.

These are documented options and proposed checks. Inspect the actual source application before adopting a transition.

Browser and installed clients

Inspect the source

Supported installed APK versions/OS levels, DTOs/errors, auth, deep links and delayed old-client operations.

During coexistence

Recommendation: Deploy backward-compatible server behavior first; run old/new clients against it and retain contracts through explicit adoption/support gates.

Target design

Recommendation: Version API DTOs independently of Room entities and gate optional features by negotiated capability; keep backend authorization authoritative.

Rollback

Disable the new capability and preserve old/new APIs; repair already-installed clients with a compatible higher-version release when needed.

Benefits and prerequisites

Client releases and backend changes can progress independently.
Requires: Installed-version inventory, shared API fixtures and support ownership.
Tradeoffs: Maintaining old contracts costs work; Compose adoption does not upgrade installed clients.

Checks before transfer

  • Old/new builds decode missing/unknown fields and documented errors.
  • A delayed old offline operation succeeds or returns the declared conflict.
  • Deep links, session expiry and denied resources work across supported releases.
Data and schema

Inspect the source

Room schema versions, exported schemas, all upgrade paths and user-owned unsynced records.

During coexistence

Recommendation: Add migrations without discarding old fields until adoption permits; include direct upgrades from every supported starting version.

Target design

Recommendation: Keep exported Room schemas and explicit migration tests; use automatic or manual migrations according to the actual change.

Rollback

Ship a forward repair when older code cannot read the new store; any restore requires reconciling writes made after upgrade.

Benefits and prerequisites

Reviewed migrations preserve local user data across updates.
Requires: Complete paths plus assertions on data/relationships/pending work.
Tradeoffs: Schema validation alone does not establish value preservation; destructive fallback permanently deletes table data.

Checks before transfer

  • MigrationTestHelper exercises each path and earliest-to-current upgrade.
  • Assert unsynced rows, IDs and relationships remain meaningful.
  • Verify intended downgrade behavior; prohibit destructive fallback for the only copy of user work.
Caching and shared state

Inspect the source

Local source-of-truth reads, pending offline operations, revision/cursor/tombstone and conflict rules.

During coexistence

Recommendation: Use one repository and one reconciler for each data category; persist queued mutations with operation IDs before reporting success.

Target design

Recommendation: Use Room-backed local reads and a durable operation queue when offline writes are needed; select push/pull/hybrid sync and a domain-specific conflict policy.

Rollback

Stop the new synchronizer and retain its queue; map remaining operations/cursors to old semantics before resume.

Benefits and prerequisites

Cached local data can keep required reads available and preserve offline intent.
Requires: Server sync support, retry-safe writes and an explicit conflict policy.
Tradeoffs: Offline-first reads do not imply offline writes; last-write-wins can discard intent and depends on metadata semantics.

Checks before transfer

  • Two offline devices make conflicting changes and obtain the documented resolution.
  • Kill process after local commit and before network send; operation survives.
  • Repeat server-committed/unacknowledged mutation without duplication; honor deletes/tombstones.
Jobs and schedules

Inspect the source

Existing queued Worker class names, input Data versions, unique-work identities and external effects.

During coexistence

Recommendation: Retain readers/worker entry points for already-enqueued payloads; transfer one job category and prevent duplicate schedulers.

Target design

Recommendation: Use WorkManager for eligible persistent device work with constraints/backoff; keep server completion obligations in a backend durable job.

Rollback

Cancel new scheduling and account for pending/running work; keep compatibility workers until drained or safely transformed.

Benefits and prerequisites

Scheduled work persists across restart and device reboot.
Requires: Compatible worker payloads, stable unique names, replay-safe effects and documented constraints.
Tradeoffs: Scheduling windows/power policy affect timing; unique work does not provide exactly-once remote effects.

Checks before transfer

  • Reboot with queued work and verify it resumes when constraints permit.
  • Update app with old queued input; the new worker decodes it.
  • Crash after remote effect/before acknowledgement and confirm retry is safe.
Identity and authorization

Inspect the source

Existing stored credential encryption, key alias/access constraints, backup exclusions, issuer expiry/revocation.

During coexistence

Recommendation: Retain the backend issuer and read old encrypted credential formats during a bounded transition; isolate account-local data.

Target design

Recommendation: Use Keystore-backed cryptographic keys with reviewed encrypted app-private credential storage and backend revocation.

Rollback

Retain compatibility decryption only while secure; require sign-in if the old format/key cannot be restored safely.

Benefits and prerequisites

OS key protection can reduce key extraction exposure.
Requires: Qualified device/API support, key invalidation handling and a reviewed storage/backup policy.
Tradeoffs: Keystore stores keys rather than arbitrary tokens; device protection does not replace backend authorization.

Checks before transfer

  • Key invalidation produces a controlled sign-in path.
  • Backup/restore cannot silently expose unusable/plain credentials.
  • Sign-out/account switch stops old-account queued work and backend revocation is enforced.
Delivery and recovery

Inspect the source

Play tracks, signing/version codes, adoption, crash metrics and schema/API compatibility.

During coexistence

Recommendation: Verify an internal/closed-track update over supported old builds and preserve compatible backend behavior for the whole mixed-version population.

Target design

Recommendation: Use a staged rollout with explicit crash/data-loss thresholds and a higher-version compatible repair path.

Rollback

Halt additional rollout and disable the affected capability if safe; publish a compatible fixed build without assuming a downgrade of installed clients.

Benefits and prerequisites

Percentage-based adoption can limit initial exposure.
Requires: Signed build, account/track owner, telemetry and recovery artifact.
Tradeoffs: Halting only prevents further rollout; users who already updated remain on that version.

Checks before transfer

  • Install-over-old-build retains unsynced data and auth.
  • Track failures by app/OS/schema version.
  • Halt/recovery rehearsal keeps already-updated users functional.

Read the migration workflow →

Setup

  1. Start in a source checkout in a local development workspace. Record package ID, minimum/target SDK, supported form factors, API contract, permissions and release owner.
  2. Install Android Studio and compatible SDK/JDK; use the project Gradle wrapper. Create a Kotlin Empty Activity/Compose project. Pin Gradle/AGP/Kotlin/Compose and library versions in a version catalog.
  3. Implement screen state/ViewModels and repositories. Add Room migrations, a typed HTTP client and deterministic fake data. Keep secrets out of version control and app resources.
  4. Run JVM tests, lint and a debug build. Start a documented emulator or attach an authorized device, then run instrumentation/Compose checks.
  5. Exercise process death, offline/reconnect, rotation, permission denial, TalkBack, large fonts and each supported form factor on appropriate devices.
  6. Account owner establishes package ownership, upload key and Play App Signing. Build a signed release bundle, verify internal-track install/update, then use the authorized release workflow; retain reports and distribution receipts.

Verification

Run these in the configured application; they are instructions, not recorded passes.

./gradlew --version
./gradlew tasks --all
./gradlew :app:lintDebug :app:testDebugUnitTest :app:assembleDebug
adb devices
./gradlew :app:connectedDebugAndroidTest
./gradlew :app:bundleRelease

Open questions

Sources