
Mvi Architecture
- 37 installs
- 60 repo stars
- Updated June 14, 2026
- ahmed3elshaer/everything-claude-code-mobile
mvi-architecture is a Claude Code skill that provides Model-View-Intent architecture patterns for Android with unidirectional data flow, state management, and side effects.
About
mvi-architecture is a Claude Code skill that documents the Model-View-Intent pattern for Android. It shows how to model immutable State, sealed Intent and SideEffect types, and a ViewModel that reduces intents into StateFlow updates and emits effects through a Channel. Developers use it when structuring a Compose screen with unidirectional data flow. It includes the Compose UI wiring for state collection and side-effect handling.
- Model-View-Intent unidirectional data flow for Android
- State, Intent, and SideEffect modeling with StateFlow and a Channel
- Compose UI integration with collectAsStateWithLifecycle and LaunchedEffect
Mvi Architecture by the numbers
- 37 all-time installs (skills.sh)
- Ranked #644 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
mvi-architecture capabilities & compatibility
- Capabilities
- state management · ui architecture · compose screens
- Use cases
- frontend · refactoring
What mvi-architecture says it does
Unidirectional data flow architecture for Android.
val state by viewModel.state.collectAsStateWithLifecycle()
npx skills add https://github.com/ahmed3elshaer/everything-claude-code-mobile --skill mvi-architectureAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 37 |
|---|---|
| repo stars | ★ 60 |
| Last updated | June 14, 2026 |
| Repository | ahmed3elshaer/everything-claude-code-mobile ↗ |
What it does
Structure Android Compose screens with Model-View-Intent unidirectional data flow, state, intents, and side effects.
Who is it for?
Structuring Android Compose screens with unidirectional MVI data flow
Skip if: Non-Compose or non-Android UI architectures
When should I use this skill?
Building a new Compose screen or refactoring one to unidirectional data flow
What you get
Screens use immutable state, sealed intents, and a side-effect channel wired cleanly into Compose
By the numbers
- Models 3 core primitives (State, Intent, SideEffect)
Files
MVI Architecture
Unidirectional data flow architecture for Android.
Core Concepts
Intent → ViewModel → State → UI
↑ │
└────────────────────────┘State
@Immutable
data class HomeState(
val isLoading: Boolean = false,
val items: List<Item> = emptyList(),
val error: ErrorState? = null,
val searchQuery: String = ""
) {
sealed interface ErrorState {
data class Network(val message: String) : ErrorState
data object Unauthorized : ErrorState
}
}Intent
sealed interface HomeIntent {
object LoadItems : HomeIntent
object Refresh : HomeIntent
data class Search(val query: String) : HomeIntent
data class ItemClicked(val id: String) : HomeIntent
object ClearError : HomeIntent
}Side Effects
sealed interface HomeSideEffect {
data class NavigateToDetail(val itemId: String) : HomeSideEffect
data class ShowSnackbar(val message: String) : HomeSideEffect
object NavigateToLogin : HomeSideEffect
}ViewModel
class HomeViewModel(
private val getItemsUseCase: GetItemsUseCase
) : ViewModel() {
private val _state = MutableStateFlow(HomeState())
val state: StateFlow<HomeState> = _state.asStateFlow()
private val _sideEffects = Channel<HomeSideEffect>(Channel.BUFFERED)
val sideEffects: Flow<HomeSideEffect> = _sideEffects.receiveAsFlow()
fun onIntent(intent: HomeIntent) {
when (intent) {
is HomeIntent.LoadItems -> loadItems()
is HomeIntent.Refresh -> loadItems(refresh = true)
is HomeIntent.Search -> search(intent.query)
is HomeIntent.ItemClicked -> {
viewModelScope.launch {
_sideEffects.send(HomeSideEffect.NavigateToDetail(intent.id))
}
}
is HomeIntent.ClearError -> _state.update { it.copy(error = null) }
}
}
private fun loadItems(refresh: Boolean = false) {
viewModelScope.launch {
if (!refresh) _state.update { it.copy(isLoading = true) }
getItemsUseCase()
.onSuccess { items ->
_state.update { it.copy(isLoading = false, items = items, error = null) }
}
.onFailure { error ->
_state.update { it.copy(isLoading = false, error = mapError(error)) }
}
}
}
private fun mapError(error: Throwable): HomeState.ErrorState {
return when (error) {
is UnauthorizedException -> HomeState.ErrorState.Unauthorized
else -> HomeState.ErrorState.Network(error.message ?: "Unknown error")
}
}
}UI Integration
@Composable
fun HomeScreen(
viewModel: HomeViewModel = koinViewModel(),
onNavigateToDetail: (String) -> Unit,
onNavigateToLogin: () -> Unit
) {
val state by viewModel.state.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
// Handle side effects
LaunchedEffect(Unit) {
viewModel.sideEffects.collect { effect ->
when (effect) {
is HomeSideEffect.NavigateToDetail -> onNavigateToDetail(effect.itemId)
is HomeSideEffect.ShowSnackbar -> snackbarHostState.showSnackbar(effect.message)
is HomeSideEffect.NavigateToLogin -> onNavigateToLogin()
}
}
}
// Load data
LaunchedEffect(Unit) {
viewModel.onIntent(HomeIntent.LoadItems)
}
HomeContent(
state = state,
onIntent = viewModel::onIntent,
snackbarHostState = snackbarHostState
)
}
@Composable
private fun HomeContent(
state: HomeState,
onIntent: (HomeIntent) -> Unit,
snackbarHostState: SnackbarHostState
) {
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) }
) { padding ->
when {
state.isLoading -> LoadingIndicator()
state.error != null -> ErrorContent(
error = state.error,
onRetry = { onIntent(HomeIntent.LoadItems) }
)
else -> ItemList(
items = state.items,
onItemClick = { onIntent(HomeIntent.ItemClicked(it)) }
)
}
}
}Testing
@Test
fun `when LoadItems succeeds, state contains items`() = runTest {
val items = listOf(Item("1", "Test"))
coEvery { getItemsUseCase() } returns Result.success(items)
viewModel.state.test {
awaitItem() // Initial
viewModel.onIntent(HomeIntent.LoadItems)
awaitItem().isLoading shouldBe true
awaitItem().items shouldBe items
}
}---
Remember: MVI = predictable state, testable logic, debuggable flow.
Related skills
FAQ
How are side effects handled?
The ViewModel emits HomeSideEffect through a buffered Channel exposed as a Flow, collected in a LaunchedEffect in Compose.
How is state exposed?
As a MutableStateFlow reduced from intents and exposed as a read-only StateFlow, collected with collectAsStateWithLifecycle.