# Android Architecture Showcase A single runnable **Android-only (Jetpack Compose)** reference app that demonstrates good architecture conventions — each in its own module/example. Teaching repo: every module is meant to be minimal but complete and idiomatic. > **Status:** built milestone-by-milestone from the > [Linear backlog](https://linear.app/adrian-kuta/project/android-architecture-showcase-b5ecdeddda6c). > **Foundation**, **Core Infrastructure**, the **Flagship MVI** characters feature, and > **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 Multi-module Gradle + `build-logic` convention plugins · Koin (constructor DSL) · Ktor · KotlinX Serialization · Coil · Timber · type-safe Compose Navigation. Data comes from the no-key [Rick & Morty API](https://rickandmortyapi.com/). What it showcases: **MVI** as the primary presentation pattern (flagship *characters* feature), an **MVVM** contrast screen (*about*), and the same MVI `ViewModel` driven by **two renderers** — Jetpack Compose and classic **XML + ViewBinding + RecyclerView** — proving the presentation logic is UI-toolkit-agnostic. See [Presentation patterns](#presentation-patterns-mvi-vs-mvvm) below. ## Module structure ``` :app → wires everything; single Activity, Compose host :build-logic → Gradle convention plugins (the only place versions/config live) :core:domain → Result/error types, shared domain models (pure Kotlin) :core:data → Ktor HttpClient factory, safe-call helpers :core:presentation → UiText, ObserveAsEvents, DataError → UiText :core:design-system → AppTheme + reusable composables :feature:characters:domain → models + repository interface (pure Kotlin) :feature:characters:data → DTOs, mappers, data source, repository impl :feature:characters:presentation → MVI ViewModel/State/Action/Event (UI-agnostic: no Compose, no Views) :feature:characters:presentation-compose → Compose renderer :feature:characters:presentation-views → Views/XML renderer (same ViewModel) :feature:about:presentation → MVVM contrast screen ``` **Dependency rules:** `presentation → domain ← data`; `domain` depends only on `:core:domain`; 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 ```bash ./gradlew assembleDebug # build the APK ./gradlew projects # print the module tree ./gradlew check # tests + lint (added in the Quality & Docs milestone) ``` Requires JDK 17+ (the Gradle build pins a Java 17 toolchain) and the Android SDK (`compileSdk 36`, `minSdk 24`).