Add an architecture.android.unit.test convention plugin that runs local unit
tests on the JUnit5 platform via useJUnitPlatform() (AndroidUnitTest extends
Gradle's Test) + the unit-test bundle. Deliberately NOT using the
de.mannodermaus plugin (targets AGP 8.x; we're on AGP 9). Add
junit-platform-launcher (Gradle 9 dropped the bundled launcher); set the
instrumentation runner; add a compose-ui-test bundle pinning espresso/runner to
current versions (transitive espresso 3.5.0 calls the removed
InputManager.getInstance() on API 34+). CI now runs ./gradlew test and compiles
the instrumented tests. Drop unused testing catalog entries.
Add a CharacterDetail MVI stack (State/Action/Event/ViewModel + CharacterDetailUi)
to the UI-agnostic :feature:characters:presentation. The detail ViewModel reads the
typed characterId from SavedStateHandle (populated by the type-safe CharacterDetailRoute),
so the module keeps zero navigation/Compose deps.
Add CharacterDetailScreen (Root/Screen, image header, attribute rows, loading/error)
and CharacterDetailRoute to :presentation-compose; refactor charactersGraph to drive
list->detail via NavController and expose About / Views entries as callbacks. Extract
shared CharacterStatus label/colour helpers; add an overflow menu to the list app bar.
Add material-icons-core to the compose bundle for the app-bar icons.
- CharacterListState (characters, isLoading, isLoadingNextPage, currentPage, endReached, error: UiText?),
CharacterListAction (OnCharacterClick/OnRetry/OnLoadNextPage), CharacterListEvent (NavigateToDetail/ShowSnackbar).
- CharacterListViewModel: state via .update, one-time events via Channel, DataError -> UiText on failure,
pagination persisted in SavedStateHandle (rebuilds list up to the saved page after process death).
- CharacterUi + Character.toCharacterUi().
- NO Compose/Views deps: verified no androidx.compose on the compile classpath. Stability via
ImmutableList instead of @Stable (which would require compose-runtime) — the only compose-named
transitive is kotlinx-immutable's annotations-only stub, not the Compose framework.
Android-only project, so Timber (the de-facto Android logging lib) fits better than the
KMP-oriented Kermit.
- Catalog: kermit -> timber (com.jakewharton.timber:timber 5.0.1).
- core:data: Ktor Logging bridged to Timber; safeCall logs via Timber.
- app: plant Timber.DebugTree() in debug only (buildConfig enabled for BuildConfig.DEBUG).
Foundation milestone (REDI-78, REDI-79):
- Multi-module skeleton: :app, :core:{domain,data,presentation,design-system},
:feature:characters:{domain,data,presentation,presentation-compose,presentation-views},
:feature:about:presentation, plus the :build-logic composite build.
- gradle/libs.versions.toml as the single source of truth ([versions]/[libraries]/
[bundles]/[plugins]); no inline versions in any build file.
- Convention plugins: architecture.android.{application,library,feature,feature.views},
domain.module, compose, koin, ktor, kotlinx.serialization.
- Pure-Kotlin domain modules; presentation-compose uses android.feature;
presentation-views uses android.feature.views (ViewBinding on, Compose off);
the UI-agnostic :presentation has neither Compose nor Views deps.
- Toolchain: AGP 9.0.1, Kotlin 2.3.20, Gradle 9.1.0, compileSdk 36, minSdk 24, Java 17.
- Minimal MainActivity placeholder; CI (assembleDebug) via GitHub Actions.
Verified: ./gradlew projects lists the full tree and ./gradlew assemble is green.