Deserialization happens in responseToResult via response.body<T>() on the 2xx branch. That call was outside safeCall's try/catch, so a malformed 2xx body threw an uncaught SerializationException (crash) instead of mapping to DataError.Network.SERIALIZATION. Move responseToResult(execute()) inside the try so both transport and parse errors are typed. Found by the milestone review.
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. Foundation (scaffold, version catalog,
build-logicconvention plugins) is 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 · Kermit · type-safe Compose Navigation. Data comes from the no-key
Rick & Morty API.
What it will showcase: MVI as the primary presentation pattern (flagship characters feature),
an MVVM contrast screen, and the same MVI ViewModel driven by two renderers — Jetpack
Compose and classic XML + ViewBinding + RecyclerView — proving the presentation logic is
UI-toolkit-agnostic.
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.
Build & run
./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).