Skip to content

add MVI screen pattern skill and ObserverAsEvent helper - #119

Merged
joelkanyi merged 1 commit into
mainfrom
feature/mvi-screen-skill
Jun 10, 2026
Merged

add MVI screen pattern skill and ObserverAsEvent helper#119
joelkanyi merged 1 commit into
mainfrom
feature/mvi-screen-skill

Conversation

@joelkanyi

Copy link
Copy Markdown
Collaborator

Establishes the screen architecture we follow going forward, and ships the small helper the pattern depends on.

What's in

ObserverAsEvent in uicomponent/src/main/java/com/android/swingmusic/uicomponent/presentation/util/. Lifecycle-aware effect collector. Ties collection to the STARTED state and uses Dispatchers.Main.immediate so navigation feels synchronous and effects don't fire while the screen is hidden.

ObserverAsEvent(viewModel.uiEffect) { effect ->
    when (effect) { ... }
}

Added lifecycle-runtime-compose to the version catalog and to uicomponent's deps so we can use androidx.lifecycle.compose.LocalLifecycleOwner and repeatOnLifecycle. Existing lifecycle version (2.8.7) already supports it, no version bump.

.claude/skills/mvi-screen.md — the full pattern with templates for each of the five files (Screen + ScreenContent, ViewModel, UiState, UiEvent, UiEffect). Invoke as /mvi-screen for the templates. Key conventions baked in:

  • Initial data fetches go in the VM init { } block. No LaunchedEffect for fetches in the Screen.
  • One-shot effects flow through a Channel<UiEffect> and are always collected with ObserverAsEvent. Never raw LaunchedEffect { effects.collect { } }.
  • No loadedInitialData flag, the VM lifecycle handles it.
  • ScreenContent is stateless so it can be @Previewd. The outer Screen binds the VM and effects.

CLAUDE.md — added a short pointer in the Architecture section so the pattern gets applied on every screen task without needing the skill to be explicitly invoked.

.gitignore — excludes .claude/settings.local.json (local user settings) while keeping the skills folder tracked.

Why now

While working on #104 we noticed the folder screen refetches on every back-nav. Joel asked why, and the answer surfaced that we don't have a standard pattern for "data lives in the VM, screen just reads from it." This PR sets the baseline. Future screen work follows it; existing screens can be migrated incrementally when we touch them.

Not in scope

No existing screens are migrated in this PR. That can happen one screen at a time as we work on each feature.

Establishes the architecture pattern we follow for screens going forward:
Screen + ScreenContent + ViewModel + UiState + UiEvent + UiEffect, with
initial fetches in the VM init {} block and one-shot effects collected
through a lifecycle-aware ObserverAsEvent.

- ObserverAsEvent in uicomponent/.../util/ ties effect collection to the
  STARTED lifecycle state with Dispatchers.Main.immediate, so navigation
  and snackbars only fire while the screen is visible.
- lifecycle-runtime-compose added to support androidx.lifecycle.compose
  LocalLifecycleOwner and repeatOnLifecycle.
- Full pattern with templates lives in .claude/skills/mvi-screen.md.
  CLAUDE.md carries a short pointer so the pattern gets applied on every
  screen task without needing an explicit skill invocation.
- .gitignore now excludes .claude/settings.local.json (local user file)
  while keeping the skills folder tracked.
@joelkanyi
joelkanyi merged commit 45b3667 into main Jun 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant