Decompose mvikotlin
Skill P1rnazarov/decompose-mvikotlin-skill/skills/decompose-mvikotlin
Decompose + MVIKotlin expert for Kotlin Multiplatform architecture. Use whenever the user mentions Decompose APIs (ComponentContext, childStack, childSlot, childPages, StackNavigation, SlotNavigation, instanceKeeper, retainedComponent, Value, ChildStack, pushNew, bringToFront, DefaultComponentContext), MVIKotlin APIs (Store, StoreFactory, Executor, Reducer, Bootstrapper, Intent, Label, dispatch, CoroutineExecutor, ReaktiveExecutor, coroutineExecutorFactory, LoggingStoreFactory, TimeTravelStoreFactory), MVI architecture in KMP, navigation between screens in a Decompose app, connecting stores to components, exposing component state to Compose or SwiftUI, or asks to review/refactor code built on this stack. Also use when the user asks about Clean Architecture / layering / modularization of a Decompose KMP project (domain, data, feature modules, UseCase, Koin wiring), clean code quality on this stack, or complains about slow navigation, janky transitions, dropped frames, or asks for a performance audit of navigation. Covers feature generation, code review against architecture and clean-code rules, navigation performance auditing with measurements, Compose Multiplatform UI integration, and SwiftUI (iOS) integration.From its SKILL.md
npx -y skills add P1rnazarov/decompose-mvikotlin-skill --skill decompose-mvikotlinAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
8.9 KB, as published. Nobody here has run it
Decompose + MVIKotlin (Kotlin Multiplatform)
Expert guidance for building KMP apps with Decompose (lifecycle-aware component tree + navigation) and MVIKotlin (MVI framework: Store / Executor / Reducer).
Current versions (verify before pinning): Decompose 3.5.0, MVIKotlin 4.4.0,
Essenty 2.5.0. Both libraries target Kotlin 2.2.x.
Architecture model
RootComponent (ComponentContext)
├── Child Stack ──► FeatureComponent ──► Store (via instanceKeeper)
│ │ Intent → Executor → Msg → Reducer → State
│ │ Executor → Label (one-off events)
│ └── exposes Value<Model> / StateFlow to UI
└── Child Slot ──► DialogComponent
UI layer (Compose / SwiftUI) observes components, never touches stores directly.
- Component = lifecycle scope + navigation node. Owns its Store, exposes
read-only state (
Value<T>orStateFlow<T>) and callback methods to UI. - Store = business logic container. Never exposed to UI directly.
- UI = thin layer subscribing to component state.
Golden rules
These are the mistakes most frequently made with this stack. Enforce all of them when generating code; flag all of them when reviewing.
- Store lifecycle — always
instanceKeeper.getStore { ... }. Never create a Store as a singleton, in a DI graph with app scope, or as a plain property without InstanceKeeper. Stores survive configuration changes via InstanceKeeper and are disposed when the component is destroyed. - Navigation flows up via callbacks. A child component NEVER navigates
itself and NEVER receives the
StackNavigationobject. It exposesonFinished: () -> Unit/onItemSelected: (Id) -> Unitcallbacks; the parent owns navigation and passes lambdas into child factories. Configmust be@Serializable(kotlinx-serialization), a sealed class/interface, and contain only data — never lambdas, components, or stores. Configs are persisted for process death recovery.store.accept(Intent)/ state observation on Main thread only. Executors may switch to IO internally (withContext/observeOn), butdispatch(Msg)andpublish(Label)must happen on Main. InCoroutineExecutorthescopeis already Main-dispatched — use it; never useGlobalScope.- One
ComponentContextper child. Create children only viachildContext(...),childStack(...),childSlot(...)— never pass the parent's context into a child, never create a component without a context. Reduceris pure. No side effects, no IO, no time/random reads. All effects live in the Executor; the Reducer only computesState + Msg -> State.- Labels for one-off events (navigation triggers, toasts, analytics) — never model them as state flags that the UI must "reset".
- UI never imports MVIKotlin. Compose/SwiftUI sees only the component
interface: state + callbacks. The component maps Store
Stateto UI model and StoreLabelto navigation callbacks. - Interface +
Default*implementation for every component (ListComponent/DefaultListComponent) — keeps previews and tests simple. - Don't recreate stores on back stack changes —
childStackkeeps component instances for configs in the stack; rely on that, don't cache components manually. - Component constructors are cheap. No IO, parsing, or heavy mapping in
constructors/
init— they run on the main thread during the navigation frame. Work starts from the store Bootstrapper or lifecycle. - Domain stays pure. In layered projects:
domainhas no Decompose, MVIKotlin, or framework imports; executors call use cases, not data sources; DTOs/entities never appear inStore.Stateor component models. - Feature internals are
internal. A feature module exposes only the component interface and a factory;Default*, stores, Msg/Action are hidden. runCatchingmust rethrowCancellationExceptionin suspend code — use a cancellation-safe wrapper (see architecture.md).
Review checklist
When asked to review code on this stack, check the golden rules above plus:
-
Configsealed +@Serializable, no behavior inside - No
GlobalScope, noDispatchers.Mainhardcoded inside Executors (usescope) - No Store/navigation references leaking into UI composables/views
-
Value/StateFlowexposed read-only (notMutableValue/MutableStateFlow) - Coroutine scopes come from
coroutineScope(...)(Essenty) tied to lifecycle - Back button:
BackHandlerowned by component, not UI - Child→parent communication via constructor callbacks, not shared singletons/event buses
- Process death: state that must survive uses
stateKeeper, not only InstanceKeeper
Then extend the review by domain:
- Architecture (layering, module deps, DI, error types) — checklist in references/architecture.md
- Clean code (naming, size budgets, immutability, visibility) — checklist in references/clean-code.md
- Performance (constructors, stack hygiene, subscriptions, stateKeeper payload) — audit procedure in references/performance.md
Report findings as: violation → why it breaks → corrected code.
Performance audit mode
When the user says navigation is slow/janky or asks for a performance audit, follow the audit procedure in references/performance.md: walk the 8-step checklist, collect evidence, propose at least one concrete measurement (Macrobenchmark / compiler reports / Instruments), and report findings as finding → evidence/metric → fix with P1–P3 severity. Never give generic "optimize your code" advice.
Reference routing
Read the reference file matching the task; read integration.md for any task that
touches both libraries.
| Task | Read |
|---|---|
| Components, navigation (stack/slot/pages), lifecycle, root setup | references/decompose.md |
| Store anatomy, executors (coroutines/reaktive), DSL builders, testing stores | references/mvikotlin.md |
| Wiring stores into components, labels→callbacks, asValue | references/integration.md |
| Compose UI: subscribeAsState, Children, stack animations, predictive back | references/compose-ui.md |
| SwiftUI/iOS: observing Value, ObservableObject wrappers, StackView | references/swiftui.md |
| Layering, modules, UseCases, DI (Koin), error types, testing strategy | references/architecture.md |
| Naming, size budgets, immutability, visibility, detekt setup | references/clean-code.md |
| Slow navigation, jank, recomposition waste, Macrobenchmark, audit mode | references/performance.md |
Quick decision guide
- Screen-to-screen navigation → Child Stack; dialogs/bottom sheets → Child Slot; tabs/pager → Child Pages.
- Simple feature without business logic → component with
MutableValue, no Store. - Feature with async work / business rules → component + MVIKotlin Store.
- Choose coroutines extensions by default; reaktive only if the project already uses Reaktive.
- Logging/time-travel: wrap only in debug —
LoggingStoreFactory(TimeTravelStoreFactory())behind a build flag.