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 + Clean Architecture flavour on the traditional XML view stack — Fragments, the Navigation component, ViewBinding, and a RecyclerView adapter. Each feature is an MVI store: the View sends Intents, a pure reducer derives the next immutable State, and one-shot Effects (navigation) are delivered over a Channel. It's the XML counterpart to the MVI-Clean-Compose module — identical store, reducers, and contract; only the View layer differs (Fragments collect state/effects flows instead of Compose).
- Language: Kotlin
- UI: XML layouts + ViewBinding · Fragments ·
RecyclerView - Architecture: MVI (Intent / State / Result / Effect + pure reducer) over Clean (Domain / Data / UI)
- State:
StateFlowcollected withrepeatOnLifecycle; one-shot effects over aChannel - Navigation: Jetpack Navigation component (
nav_graph.xml), driven by MVI Effects - DI: Dagger 2 (KSP) — plain Dagger, no Hilt
- Min SDK: 28 · Target/Compile SDK: 36
- Package:
com.preetanshumishra.patternkit.android.mvicleanxml
A single-user task list. One entity (TaskItem: title, optional notes, optional due date, priority, completion). Three screens:
- List (
TaskListFragment) — filter chips (All / Active / Completed), sort by due date or priority, swipe-to-delete viaRecyclerView, FAB to create. - Detail (
TaskDetailFragment) — read-only fields, toggle completion, edit, delete. - Form (
TaskFormFragment) — 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.
- Intent — everything the UI can ask for (
TaskListIntent,TaskFormIntent); the View's single entry point isonIntent(...). - Result — the outcome of handling an intent, fed to the reducer.
- State — one immutable
…UiStateexposed as aStateFlow; the Fragment collects it withrepeatOnLifecycle(STARTED)and renders. - Effect — one-shot side effects the UI can't model as state (navigation), delivered over a
Channeland collected by the Fragment. The View never decides a transition itself — e.g. tapping a row sendsTaskClicked, the store emitsNavigateToDetail. - Reducer —
reduceTaskList/reduceTaskFormare pure top-level functions (no coroutines, no I/O), so state transitions are synchronously unit-testable.
The detail and form screens feed edits back into the shared list store as intents (TaskToggled, TaskDeleted, TaskSaved) rather than mutating it directly.
Five single-responsibility use cases sit between the stores and the data layer (UseCases.kt): GetTasksUseCase, CreateTaskUseCase, UpdateTaskUseCase, DeleteTaskUseCase, ToggleTaskCompletionUseCase. Each depends only on the TaskRepository contract (in domain/). Stores translate intents into results by running use cases — they never touch the repository directly.
Plain Dagger 2, wired by hand. AppComponent (@Singleton) exposes the five use cases; RepositoryModule @Binds TaskRepository → MockTaskRepository. PatternKitApp holds the component; a hand-rolled ViewModelFactory pulls the use cases out of the graph and constructs each store.
app/src/main/
├── kotlin/.../mvicleanxml/
│ ├── domain/ # TaskItem, Priority, TaskFilter, TaskSort,
│ │ # TaskRepository (contract), UseCases.kt
│ ├── data/ # MockTaskRepository, seed data
│ ├── di/ # AppComponent, RepositoryModule
│ ├── ui/
│ │ ├── list/ # TaskListFragment + TaskAdapter + TaskListViewModel (store + reducer)
│ │ ├── detail/ # TaskDetailFragment
│ │ ├── form/ # TaskFormFragment + TaskFormViewModel (store + reducer)
│ │ ├── NavArgs.kt, MainActivity, ViewModelFactory
│ └── PatternKitApp.kt
└── res/
├── layout/ # activity_main, fragment_list/detail/form, item_task
├── menu/ # menu_list, menu_detail
└── navigation/ # nav_graph.xml
./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.