agentsclimarketplace

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

Install
npx -y skills add P1rnazarov/decompose-mvikotlin-skill --skill decompose-mvikotlin

Assembled 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> or StateFlow<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.

  1. 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.
  2. Navigation flows up via callbacks. A child component NEVER navigates itself and NEVER receives the StackNavigation object. It exposes onFinished: () -> Unit / onItemSelected: (Id) -> Unit callbacks; the parent owns navigation and passes lambdas into child factories.
  3. Config must be @Serializable (kotlinx-serialization), a sealed class/interface, and contain only data — never lambdas, components, or stores. Configs are persisted for process death recovery.
  4. store.accept(Intent) / state observation on Main thread only. Executors may switch to IO internally (withContext / observeOn), but dispatch(Msg) and publish(Label) must happen on Main. In CoroutineExecutor the scope is already Main-dispatched — use it; never use GlobalScope.
  5. One ComponentContext per child. Create children only via childContext(...), childStack(...), childSlot(...) — never pass the parent's context into a child, never create a component without a context.
  6. Reducer is pure. No side effects, no IO, no time/random reads. All effects live in the Executor; the Reducer only computes State + Msg -> State.
  7. Labels for one-off events (navigation triggers, toasts, analytics) — never model them as state flags that the UI must "reset".
  8. UI never imports MVIKotlin. Compose/SwiftUI sees only the component interface: state + callbacks. The component maps Store State to UI model and Store Label to navigation callbacks.
  9. Interface + Default* implementation for every component (ListComponent / DefaultListComponent) — keeps previews and tests simple.
  10. Don't recreate stores on back stack changes — childStack keeps component instances for configs in the stack; rely on that, don't cache components manually.
  11. 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.
  12. Domain stays pure. In layered projects: domain has no Decompose, MVIKotlin, or framework imports; executors call use cases, not data sources; DTOs/entities never appear in Store.State or component models.
  13. Feature internals are internal. A feature module exposes only the component interface and a factory; Default*, stores, Msg/Action are hidden.
  14. runCatching must rethrow CancellationException in 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:

  • Config sealed + @Serializable, no behavior inside
  • No GlobalScope, no Dispatchers.Main hardcoded inside Executors (use scope)
  • No Store/navigation references leaking into UI composables/views
  • Value/StateFlow exposed read-only (not MutableValue/MutableStateFlow)
  • Coroutine scopes come from coroutineScope(...) (Essenty) tied to lifecycle
  • Back button: BackHandler owned 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:

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.

TaskRead
Components, navigation (stack/slot/pages), lifecycle, root setupreferences/decompose.md
Store anatomy, executors (coroutines/reaktive), DSL builders, testing storesreferences/mvikotlin.md
Wiring stores into components, labels→callbacks, asValuereferences/integration.md
Compose UI: subscribeAsState, Children, stack animations, predictive backreferences/compose-ui.md
SwiftUI/iOS: observing Value, ObservableObject wrappers, StackViewreferences/swiftui.md
Layering, modules, UseCases, DI (Koin), error types, testing strategyreferences/architecture.md
Naming, size budgets, immutability, visibility, detekt setupreferences/clean-code.md
Slow navigation, jank, recomposition waste, Macrobenchmark, audit modereferences/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.

Keep looking

Skills are one crate of 628,853. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.