docs: README MVI-vs-MVVM section + two-renderer overview
Add a Presentation patterns (MVI vs MVVM) section with a comparison table and the 'one ViewModel, two renderers' explanation, and update the status/stack (Timber, not Kermit; Breadth & Contrast complete).
This commit is contained in:
46
README.md
46
README.md
@@ -6,19 +6,21 @@ be minimal but complete and idiomatic.
|
|||||||
|
|
||||||
> **Status:** built milestone-by-milestone from the
|
> **Status:** built milestone-by-milestone from the
|
||||||
> [Linear backlog](https://linear.app/adrian-kuta/project/android-architecture-showcase-b5ecdeddda6c).
|
> [Linear backlog](https://linear.app/adrian-kuta/project/android-architecture-showcase-b5ecdeddda6c).
|
||||||
> **Foundation** (scaffold, version catalog, `build-logic` convention plugins) is complete and the
|
> **Foundation**, **Core Infrastructure**, the **Flagship MVI** characters feature, and
|
||||||
> project assembles green. Full architecture docs land with the *Quality & Docs* milestone.
|
> **Breadth & Contrast** (character detail, the MVVM About screen, the Views renderer, and
|
||||||
|
> Compose↔View interop) are complete and the project assembles green. Full architecture docs land
|
||||||
|
> with the *Quality & Docs* milestone.
|
||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
Multi-module Gradle + `build-logic` convention plugins · Koin (constructor DSL) · Ktor ·
|
Multi-module Gradle + `build-logic` convention plugins · Koin (constructor DSL) · Ktor ·
|
||||||
KotlinX Serialization · Coil · Kermit · type-safe Compose Navigation. Data comes from the no-key
|
KotlinX Serialization · Coil · Timber · type-safe Compose Navigation. Data comes from the no-key
|
||||||
[Rick & Morty API](https://rickandmortyapi.com/).
|
[Rick & Morty API](https://rickandmortyapi.com/).
|
||||||
|
|
||||||
What it will showcase: **MVI** as the primary presentation pattern (flagship *characters* feature),
|
What it showcases: **MVI** as the primary presentation pattern (flagship *characters* feature),
|
||||||
an **MVVM** contrast screen, and the same MVI `ViewModel` driven by **two renderers** — Jetpack
|
an **MVVM** contrast screen (*about*), and the same MVI `ViewModel` driven by **two renderers** —
|
||||||
Compose and classic **XML + ViewBinding + RecyclerView** — proving the presentation logic is
|
Jetpack Compose and classic **XML + ViewBinding + RecyclerView** — proving the presentation logic is
|
||||||
UI-toolkit-agnostic.
|
UI-toolkit-agnostic. See [Presentation patterns](#presentation-patterns-mvi-vs-mvvm) below.
|
||||||
|
|
||||||
## Module structure
|
## Module structure
|
||||||
|
|
||||||
@@ -40,6 +42,36 @@ UI-toolkit-agnostic.
|
|||||||
**Dependency rules:** `presentation → domain ← data`; `domain` depends only on `:core:domain`;
|
**Dependency rules:** `presentation → domain ← data`; `domain` depends only on `:core:domain`;
|
||||||
features never depend on other features; `:app` wires the graph.
|
features never depend on other features; `:app` wires the graph.
|
||||||
|
|
||||||
|
## Presentation patterns (MVI vs MVVM)
|
||||||
|
|
||||||
|
Both patterns live side by side so the trade-off is concrete, not theoretical.
|
||||||
|
|
||||||
|
| | **MVI** (`:feature:characters:*`) | **MVVM** (`:feature:about:presentation`) |
|
||||||
|
|---|---|---|
|
||||||
|
| State | one immutable `State` data class | one immutable `State` data class |
|
||||||
|
| User input | a single `onAction(Action)` funnel + sealed `Action` | plain public methods on the `ViewModel` |
|
||||||
|
| Side effects | one-time `Event`s via a `Channel` (nav, snackbar) | none — the screen calls a method / uses `LocalUriHandler` directly |
|
||||||
|
| Best when | state is complex and interacting; effects matter | the screen is small and mostly static |
|
||||||
|
|
||||||
|
The flagship characters list is MVI because its state is genuinely complex — pagination, loading
|
||||||
|
vs. next-page loading, error surfacing, and `SavedStateHandle` restore after process death — and it
|
||||||
|
emits navigation/snackbar effects. The About screen is deliberately MVVM: a `StateFlow` plus a couple
|
||||||
|
of public methods, with **no `Action` and no `Event` types at all**, because that ceremony would be
|
||||||
|
pure overhead for static content. Rule of thumb: **reach for MVI when state is complex and side
|
||||||
|
effects matter; reach for MVVM when the screen is simple.**
|
||||||
|
|
||||||
|
### One ViewModel, two renderers
|
||||||
|
|
||||||
|
`:feature:characters:presentation` is **UI-toolkit-agnostic** — it has no Compose *and* no Views
|
||||||
|
dependency (state stays Compose-stable via `kotlinx-collections-immutable` rather than `@Stable`).
|
||||||
|
The exact same `CharacterListViewModel` (State/Action/Event/UI-model) is rendered twice:
|
||||||
|
|
||||||
|
- `:feature:characters:presentation-compose` — Jetpack Compose (`LazyColumn`).
|
||||||
|
- `:feature:characters:presentation-views` — `Fragment` + ViewBinding + `RecyclerView`/`DiffUtil`.
|
||||||
|
|
||||||
|
`:app` hosts the Views renderer inside the Compose `NavHost` via `AndroidFragment` (Compose↔View
|
||||||
|
interop) and injects all navigation as callbacks, so the renderers stay decoupled from each other.
|
||||||
|
|
||||||
## Build & run
|
## Build & run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
Reference in New Issue
Block a user