Use when working on Android or KMP projects to apply senior Android engineering knowledge and best practices
You are a senior Android engineer. Apply the following guidelines to all Android and KMP work.
AndroidView / ComposeView.collectAsStateWithLifecycle to observe state from ViewModels in composables.StateFlow / State to manage UI state.kotlinx.serialization for network model serialization.Android-only projects:
KMP shared code:
For implementation detail, defer to the compose. Key architectural decisions:
remember, Modifiers, side effects, navigation), the compose is the authoritative source.For implementation detail, defer to the kotlin-coroutines and kotlin-flows skills. Key decisions:
LiveData in new code.viewModelScope for ViewModel coroutines; inject CoroutineDispatcher for testability.StateFlow for UI state, Flow for streams, suspend functions for one-shot calls.libs.versions.toml) and Kotlin script (.kts) for all Gradle files.jvmToolchain(21) (fallback: 17).app/src/main/baseline-prof.txt) for production apps to improve startup and scroll performance.feature/data, feature/domain, feature/presentation) over horizontal shared packages. Create shared packages only when truly shared.data/api, API models: data/api/models.data/cache, cache models: data/cache/models.presentation/ui: Compose-only code.presentation/models: UI models and mappers.Follow a feature-vertical module structure:
:app ← entry point, wires features together
:core:model ← shared domain models (pure Kotlin, no Android deps)
:core:data ← repositories, data sources, Room DB, Retrofit
:core:domain ← use cases, repository interfaces
:core:ui ← shared composables, theme, design system
:feature:<name> ← self-contained feature: own UI, ViewModel, nav entry point
:feature:* modules depend on :core:domain and :core:ui — never on each other:core:model have zero Android framework dependenciesandroid-gradle-logic skill for Convention Plugin setup to share build config across modulesCompose → ViewModel → Repository → Data sources
data layer.presentation layer.Add a domain layer when business logic outgrows the ViewModel or business rules belong to domain models:
models, use cases under usecases, repository interfaces under repositories.Result<T>).Error propagation by layer:
IOException, HttpException, SQLiteException, etc.).NetworkException, CacheException). Never let raw data-layer exceptions leak past this boundary.Result<T>, where the error type is a domain model. This is the Result boundary: use cases never catch platform exception types.Result<T> and map to UI state.When there is no domain layer (simple MVVM): the repository returns Result<T> directly, mapping platform exceptions to domain error models itself. The ViewModel handles Result<T> without knowing about platform exceptions.
navigation-compose 2.8+ with type-safe @Serializable route objects (not string routes).MainActivity). Navigate via NavController — never from the ViewModel directly.Channel + receiveAsFlow(), not SharedFlow.compose/references/navigation.md for full patterns.CoroutineWorker for suspend-friendly workers.Constraints (network, charging) rather than implementing retry logic manually.val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.HOURS)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"sync",
ExistingPeriodicWorkPolicy.KEEP,
syncRequest
)
For implementation detail, defer to the android-tdd skill. Key decisions:
ComposeTestRule.expect/actual for platform-specific implementations (e.g. file I/O, push tokens, biometrics).CoroutineDispatcher everywhere — Dispatchers.Main is not guaranteed on all KMP targets without the -ktx libraries.