Android & Compose Architecture Standards
SkillDev toolsArchitecture, Jetpack Compose, Navigation3 KMP, and Koin DI rules for Android apps.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Android & Compose Architecture Standards skill
What this skill tells your AI
The instructions your AI receives, as published by abivan-tech/opencode-agentic-workflows in .agents/skills/android/SKILL.md and read by ahel’s review.
Use this skill for Android work built with Kotlin, Compose, and related modern app architecture patterns.
1. Jetpack Compose UI Rules
- Model-View-Intent (MVI) & UDF:
- Model: Expose a single, immutable
ViewState(Data Class) from the ViewModel. Do not expose multiple independent state flows unless strictly isolated. - View: A pure function rendering the Model. State flows down.
- Intent: User actions are routed to the ViewModel as explicit Intents/Events. Events flow up.
- Model: Expose a single, immutable
- Screen vs View Approach:
<Name>Screen: Handles DI, Navigation3 KMP routing, and connects theViewModelto the UI.<Name>View(state, onEvent): A completely pure, stateless Composable.
- Minimize Logic in Compose:
- Do NOT use
rememberfor business logic. Business logic belongs in the ViewModel.
- Do NOT use
- Performance:
- Assume Strong Skipping Mode is enabled. Do not manually wrap lambdas in
remember. - Pass modifier chains explicitly:
modifier: Modifier = Modifier.
- Assume Strong Skipping Mode is enabled. Do not manually wrap lambdas in
- Visibility:
@PreviewComposables MUST beprivateor restricted visibility.- Do NOT make Composable functions
publicunless intended as an external design system component.
2. Navigation3 KMP (Routing & State)
If using Navigation3 KMP for architecture:
- ViewModel = Logic + State: The ViewModel handles all business logic, manages the CoroutineScope, and exposes state to the UI.
- ViewModel Interface:
- Public functions inside a ViewModel MUST represent user intents/events and generally return
Unit. - Any function computing values internally should be
private. - State should be exposed as an immutable
StateFlow.
- Public functions inside a ViewModel MUST represent user intents/events and generally return
- Routing (Navigation3 KMP):
- Treat navigation strictly as state management (part of the ViewModel or Component).
- Keep navigation execution strictly inside the
<Name>Screenwrapper, NOT inside the pure<Name>View. - Use strongly typed destinations (Objects or Data Classes).
- Crucial: Apply polymorphic
@Serializableannotations to your destination keys so they serialize correctly across non-JVM platforms (iOS/Web).
3. Dependency Injection (Koin)
If using Koin:
- Prefer
singleandfactoryinstead ofbindwith generic provider/singleton blocks for clearer syntax and safety.
4. Project Structure & Layering
Prefer a clear 3-layer layout and keep boundaries strict:
data/: implementations (local DB, remote APIs, repository impls)domain/: pure business logic (models, repository interfaces, use cases)ui/: Compose screens, reusable components, themedi/: DI wiring only (no business logic)
Rule: UI depends on domain, domain depends on nothing, data depends on domain (interfaces).
5. Coroutines, Flow, and State
- UI state:
- Use
MutableStateFlowinternally and exposeStateFlowviaasStateFlow(). - Update state via
update { it.copy(...) }to keep changes atomic.
- Use
Example (micro):
private val _state = MutableStateFlow(ViewState())
val state: StateFlow<ViewState> = _state.asStateFlow()
_state.update { it.copy(isLoading = true) }
- Long-running streams:
- Use
flowOn(Dispatchers.IO)for data layer work. - Prefer injecting a
CoroutineDispatcherfor testability.
- Use
- Error handling:
- Catch at the right boundary (usually data/usecase) and surface a user-safe error state.
6. Result Modeling (Optional)
For operations that can be loading/success/error, prefer a sealed result type over nullable juggling.
Rule: keep it small (Loading, Success(data), Error(exception)), and map/transform explicitly.
7. Testing (Unit-first)
Recommended stack (when it fits the repo):
kotlinx-coroutines-testfor deterministic coroutine testsMockKfor mockingTurbinefor testingFlow/StateFlowemissions
Rules:
- Tests must be deterministic (no real network, no timing races).
- Prefer injecting dispatchers and using test dispatchers.
- For flows: assert emission order and terminal states, not implementation details.
Example (micro):
@Test fun emitsLoadingThenData() = runTest {
// collect state/flow and assert emissions (use Turbine if available)
}
8. Lint + CI (Keep It Boring)
- Run static checks in CI:
- detekt (complexity/style)
- ktlint (formatting)
- unit tests
- Keep thresholds explicit (e.g., long method/parameter limits) and fail CI on violations.
- If the repo uses GitHub Actions, keep Android CI minimal:
- checkout
- JDK setup
- Gradle cache
- detekt/ktlint
- unit tests
- assemble (optional)
Signals
- GitHub stars
- 28
- Forks
- 3
- Last commit
- Jul 2026
Advanced
- Catalog kind
- skill
- Gateway key
android- Source
- github.com/abivan-tech/opencode-agentic-workflows