Part of PatternKit, a side-by-side reference codebase where the same small Tasks CRUD app is implemented once per architecture pattern across iOS and Android. Every module ships identical behaviour — the same domain model, the same three screens, the same mock data layer — so the only thing that varies is the architecture itself.
This module is the MVI (Model–View–Intent) + Clean Architecture flavour on Jetpack Compose. Each screen is driven by unidirectional data flow: the UI emits intents, the ViewModel reduces them into a single immutable state object, and Compose renders that state. Like the Clean module it keeps a domain layer of use cases; the difference is the strict one-way state/intent contract in the UI layer — the approach favoured for complex, state-heavy screens (Cash App's Molecule is the canonical industry example).
- Language: Kotlin
- UI: Jetpack Compose + Material 3
- Architecture: MVI + Clean (unidirectional data flow over Data / Domain / UI)
- DI: Dagger 2 (KSP) — plain Dagger, no Hilt
- Navigation: Navigation Compose
- Min SDK: 28 · Target/Compile SDK: 36
- Package:
com.preetanshumishra.patternkit.android.mvicleancompose
A single-user task list. One entity (TaskItem: title, optional notes, optional due date, priority, completion). Three screens:
- List — filter chips (All / Active / Completed), sort by due date or priority, swipe-to-delete, FAB to create.
- Detail — read-only fields, toggle completion, edit, delete.
- Form — create or edit (mode-driven), title validation (≤ 80 chars), due-date validation (not in the past), 600 ms mock async save.
Data comes from MockTaskRepository — an in-memory store seeded with ~12 tasks, with configurable artificial latency and failure rate. No real network, no local persistence — intentionally, so the architecture stays the focus.
Each ViewModel exposes a single immutable state and accepts intents (user actions) as input. State transitions happen in one place, making every screen update explicit and replayable. Beneath the UI, the same five Clean use cases drive the data flow:
GetTasksUseCase,CreateTaskUseCase,UpdateTaskUseCase,DeleteTaskUseCase,ToggleTaskCompletionUseCase
Plain Dagger 2, wired by hand:
AppComponent(@Singleton) exposes the five use cases;RepositoryModule@BindsTaskRepository→MockTaskRepository.- Use cases and the repository are constructor-injected, so Dagger builds them with no
@Providesboilerplate. PatternKitAppcreates and holds the component; a hand-rolledViewModelFactoryconstructs each ViewModel from the use cases in the graph.
app/src/main/kotlin/.../mvi/
├── data/ # MockTaskRepository, seed data
├── domain/ # TaskItem, Priority, TaskRepository (contract), UseCases
├── di/ # AppComponent, RepositoryModule
├── ui/
│ ├── list/ # TaskListScreen + TaskListViewModel (state + intents)
│ ├── detail/ # TaskDetailScreen
│ ├── form/ # TaskFormScreen + TaskFormViewModel
│ ├── nav/ # NavHost + Routes
│ ├── theme/ # Material 3 theme
│ └── ViewModelFactory.kt
└── PatternKitApp.kt
./gradlew assembleDebug # build the debug APK
./gradlew installDebug # install on a connected device/emulator
./gradlew test # unit testsOr open the project in Android Studio and run the app configuration.