| name | compose-kotlin-agent-skills |
| description | Enterprise Android/Kotlin skill for 2026 — Kotlin 2.x K2 compiler, AGP 9, Navigation 3, edge-to-edge Compose, strict MVI with atomic _state.update, banned AI antipatterns. Jetpack Compose, Hilt, Room, KMP, CameraX/ML Kit, performance, testing. Use when writing Kotlin for Android, building Compose UI, MVVM/MVI architecture, debugging recomposition, Room DAOs, Navigation 3, coroutines, or asking "structure Android app", "ViewModel UiState", "collectAsStateWithLifecycle", "edge to edge", "AGP 9", "K2 compiler", "Play Store release".
|
| license | MIT |
| metadata | {"author":"haidrrrry","version":"2.2.0","standard":"agentskills.io","platforms":{"target":["android","kotlin-multiplatform","compose-multiplatform"],"host":["macos","linux","windows"],"agent_compatibility":27}} |
Android & Kotlin — Enterprise Production Skill (2026)
Authoritative Android engineering kit. Real patterns from AnimatedClockJetpacl, Authenticator, RepLock.
Not an Android library, voice-assistant skill, on-device LLM SDK, or Compose UI library.
This is markdown for coding AIs — clone into .cursor/skills/ or .claude/skills/. See README § "What this is NOT".
Meta index: AGENTS.md · CI: python3 scripts/validate_skills.py
Sub-Skills (Modular)
2026 Toolchain — Non-Negotiable
| Tool | Minimum | Notes |
|---|
| Kotlin | 2.0+ (2.1.x) | K2 compiler only — no K1 fallback |
| AGP | 9.0+ | Built-in Kotlin, new DSL defaults |
| Compose BOM | 2025.05+ | Material 3, strong skipping default |
| Navigation | 3.x (2.9+ artifact) | Type-safe NavKey, @Serializable routes |
| compileSdk / targetSdk | 35+ | Edge-to-edge mandatory |
| JDK | 17 | Required for AGP 9 |
[versions]
kotlin = "2.1.20"
agp = "9.0.0"
compose-bom = "2025.05.00"
navigation = "2.9.0"
room = "2.7.1"
hilt = "2.56.2"
lifecycle = "2.9.0"
coroutines = "1.10.2"
Kotlin 2.x / K2 Compiler Constraints
- Enable K2 —
kotlin.compiler.execution.strategy default; use ksp not kapt for Hilt/Room.
- Smart casts — prefer
when (val x = state) { is Loaded -> ... } over unsafe !!.
- Sealed hierarchy —
sealed interface UiState + data object Loading : UiState for exhaustive when.
@JvmInline value class for IDs (@JvmInline value class UserId(val raw: String)).
@Immutable / @Stable on UiState and UI models passed to Compose.
- Explicit API optional for libraries —
kotlin { explicitApi() } in published modules.
- No
!! except in tests with comment — use requireNotNull / early return.
data class copy for state — never mutate UiState fields in place.
sealed interface AccountsUiState {
data object Loading : AccountsUiState
data class Ready(
val accounts: List<AccountUi>,
val query: String = ""
) : AccountsUiState
data class Error(@StringRes val messageRes: Int) : AccountsUiState
}
MVI Guardrails — Atomic State Only
Allowed mutation: _state.update { it.copy(...) } or _state.update { prev -> ... }.
Banned in ViewModel:
_state.value = AccountsUiState(...)
_state.value.accounts.add(item)
mutableStateOf / mutableStateListOf in VM
viewModelScope.launch { _state.value = ... }
Required pattern:
class AccountsViewModel @Inject constructor(
private val repository: AccountsRepository
) : ViewModel() {
private val _state = MutableStateFlow<AccountsUiState>(AccountsUiState.Loading)
val state: StateFlow<AccountsUiState> = _state.asStateFlow()
private val _effects = Channel<AccountsEffect>(Channel.BUFFERED)
val effects: Flow<AccountsEffect> = _effects.receiveAsFlow()
fun onEvent(event: AccountsEvent) {
when (event) {
AccountsEvent.Refresh -> refresh()
is AccountsEvent.QueryChanged -> _state.update { current ->
when (current) {
is AccountsUiState.Ready -> current.copy(query = event.query)
else -> current
}
}
}
}
private fun refresh() {
viewModelScope.launch {
_state.update { AccountsUiState.Loading }
repository.load()
.onSuccess { list ->
_state.update { AccountsUiState.Ready(accounts = list.map { it.toUi() }) }
}
.onFailure {
_state.update { AccountsUiState.Error(R.string.error_load_failed) }
}
}
}
}
UI collects:
@Composable
fun AccountsRoute(vm: AccountsViewModel = hiltViewModel()) {
val state by vm.state.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
vm.effects.collect { effect -> }
}
AccountsScreen(state = state, onEvent = vm::onEvent)
}
Navigation 3 + Edge-to-Edge
@Serializable data object Home
@Serializable data class Detail(val id: String)
@Composable
fun AppNavHost(navController: NavHostController = rememberNavController()) {
NavHost(navController, startDestination = Home) {
composable<Home> { HomeScreen(onOpen = { navController.navigate(Detail(it)) }) }
composable<Detail> { entry ->
val route = entry.toRoute<Detail>()
DetailScreen(id = route.id)
}
}
}
- Routes =
@Serializable types, not string paths
- Pass IDs in nav args — fetch models in destination ViewModel
enableEdgeToEdge() in Activity — see compose sub-skill
Deep dive: references/07-navigation.md
Banned Antipatterns — AI Hallucination Lookup
Full table with WRONG/RIGHT pairs and row numbers: references/00-banned-antipatterns.md
Load before reviewing any agent-generated Kotlin. Compose audit checklist: skills/android-kotlin-compose/SKILL.md § REVIEW MODE.
Reference Routing
| File | Topic |
|---|
references/00-banned-antipatterns.md | Master banned table — load first for reviews |
references/01-architecture.md | Clean Arch, MVVM, modules |
references/02-compose-ui.md | Composition, M3, theming |
references/03-animations.md | Motion, Canvas |
references/04-coroutines-flow.md | Flow, errors, cancellation |
references/05-hilt-di.md | DI, scopes |
references/06-room-db.md | Room, offline-first |
references/07-navigation.md | Nav 3, deep links |
references/08-kmp-cmp.md | KMP, expect/actual |
references/09-networking.md | Ktor, JWT |
references/10-performance.md | Recomposition, Coil, profiles |
references/11-testing.md | Turbine, UI tests |
references/12-camera-mlkit.md | CameraX, pose |
references/13-release-checklist.md | R8, Play Store |
references/14-datastore.md | DataStore, SP migration, encryption |
references/15-paging3.md | Paging 3, RemoteMediator, asState() |
references/16-coil-image.md | Coil 3.4, AsyncImage, cache |
references/17-accessibility.md | TalkBack, semantics, WCAG |
references/18-gradle-build-logic.md | Convention plugins, KSP, R8, baseline profiles |
references/19-xml-to-compose-migration.md | XML → Compose, RxJava → Flow |
Platforms
Target (what the docs teach): Android · Kotlin Multiplatform · Compose Multiplatform
Host (where the docs are read): macOS · Linux · Windows — anywhere your AI agent runs
Agent compatibility: 27 install guides → agents/README.md
Repo: https://github.com/haidrrrry/compose-kotlin-agent-skills
Mandatory Defaults (Summary)
- Strings:
stringResource / @StringRes — zero hardcoded UI text
- State: ViewModel
StateFlow + _state.update { }
- UI: Stateless composables,
modifier first optional param
- Lists:
LazyColumn + keys + contentType
- DI:
@HiltViewModel + constructor inject
- Tests: Fakes + Turbine — see testing sub-skill
Validation Before PR
python3 scripts/validate_skills.py --strict
Anti-Rationalizations
| Excuse | Reality |
|---|
| "UiState overkill" | data object Loading costs one line |
| "Keys later" | Broken scroll/focus NOW |
| "Prototype skip repo" | Prototypes ship |
| "collectAsState fine" | Background collector drains battery |
Examples
| Path | Patterns |
|---|
examples/animated-clock/ | Canvas, rotate(), particles |
examples/authenticator/ | Room Flow, combine, CompositionLocal theme |