7.8 KiB
7.8 KiB
Legado Compose Review Checklist
Files to Read
For a feature review, inspect the smallest complete slice:
FeatureScreen.ktand anyFeatureSheets.kt/FeatureDialogs.kt.FeatureViewModel.kt.FeatureContract.ktif present.- Host route in
MainActivity.ktor retainedFeatureActivity.kt. - Koin registration in
di/appModule.kt. - Repositories/usecases used by the feature.
- Old View caller or XML/adapters only when still used for compatibility or behavior comparison.
Use current examples as references:
ui/main/MainActivity.ktfor Navigation3 route ownership.ui/book/info/BookInfoContract.kt,BookInfoViewModel.kt,BookInfoScreen.ktfor behavior-heavy MVI/UDF.ui/book/search/SearchContract.kt,SearchViewModel.kt,SearchScreen.ktfor route-level search state.ui/widget/components/...andui/theme/...for shared UI conventions.
Architecture Checks
Flag issues when:
- A new Compose-first screen is implemented as a standalone Activity instead of a
MainActivitydestination without a compatibility reason. - A retained Activity contains feature business logic instead of acting as a thin Android
Intentcompatibility host. - Business rules, repository calls, DAO calls, persistence writes, or service orchestration live in composables.
- ViewModel exposes mutable state directly, exposes
MutableStateFlow, or lets UI mutate domain objects. - UI state is split across Activity fields, composable
rememberstate, adapter state, and ViewModel state in a way that can diverge. - One-shot events such as navigation, result codes, file opening, permission requests, or clipboard writes are represented as persistent
UiStatefields that can replay incorrectly. - MVI
FeatureIntentuser actions are confused with AndroidIntentlaunch/extras. - Domain/usecase boundaries are bypassed in new screens when a meaningful business action exists.
- A migration introduces new domain abstractions for a single trivial UI action without reducing real complexity.
UDF and State Checks
Flag issues when:
Screenfunctions own business state instead of receivingstateand callbacks.remember/rememberSaveablestores source-of-truth data that should survive process or route recreation through ViewModel state.- User-driven transient state that should survive recreation, such as a pending delete-confirmation target, is held with plain
rememberinstead ofrememberSaveableor ViewModel state. LaunchedEffectkeys are unstable or cause repeated data loading, duplicate navigation, duplicate toasts, or repeated service calls.- A long-lived
LaunchedEffectcollector captures parent callbacks or context-dependent values that can change without usingrememberUpdatedState, unless the effect is intentionally keyed to restart. - Flows are collected without lifecycle awareness in UI routes where
collectAsStateWithLifecycle()should be used. - List items lack stable keys where mutation, selection, or animation can make state attach to the wrong row.
- Heterogeneous lazy lists or grids provide
keybut omitcontentType, reducing composition reuse quality when headers, rows, ads, loading items, or expanded content mix in the same lazy layout. - Selection, search query, sorting, filtering, loading, and error states are not represented in a single coherent
UiState. - Derived values are recomputed expensively on every recomposition instead of living in ViewModel state or
remember(key). - UI effects write list-derived consistency back into the ViewModel, such as pruning selection from
LaunchedEffect(uiState.items). Prefer deriving this in the ViewModel withcombine(...)or reducing it when the data flow updates. - State is hoisted higher than the lowest common owner, such as a top-level route collecting a child ViewModel flow only needed inside a conditional branch or one popup menu.
Compose Stability and Collection Checks
Flag issues when:
- Collection-heavy
UiStateexposed to Compose uses ordinary KotlinList,Set, orMapin hot paths. Kotlin 2.x strong skipping is helpful but does not make standard Kotlin collections stable to Compose. - DAO or repository lists are passed directly through
UiStateto deep composables without conversion toImmutableList/ImmutableSet/ImmutableMapor a stable UI model wrapper. - Review recommendations imply replacing every internal collection. Keep the finding scoped to Compose-facing render state; temporary accumulators, sorting inputs, Room DAO signatures, and domain APIs can remain normal collections unless they are the actual recomposition boundary.
- Mutable collections such as
ArrayList,MutableList, or mutable maps are stored in Compose state or ViewModelUiState. - Entity classes from non-Compose modules are passed deeply through composables and cause visible recomposition churn; prefer stable UI render models if measurement or code shape shows this matters.
Navigation and Compatibility Checks
Flag issues when:
- New Compose destinations skip
MainActivityNavigation3 route registration. - Legacy Android
Intentextras or result codes change without an explicit compatibility plan. - A migrated screen is reachable both through
MainActivityand a retained Activity with inconsistent state initialization. - Navigation is performed directly inside nested composables instead of through callbacks/effects.
- Activity Result launchers, file pickers, permission requests, or Android DialogFragments are hidden inside reusable UI composables.
- Back behavior bypasses ViewModel decisions when unsaved changes, selection mode, add-to-shelf prompts, or confirmation dialogs exist.
UI and Project Convention Checks
Flag issues when:
- Raw Material components ignore
LegadoThemeor existing shared components where the project already has a wrapper. - A screen recreates shared UI already present under
ui/widget/components. - User-facing text is hardcoded instead of using string resources, except for temporary debug-only text.
- Compose UI keeps ViewBinding, adapter, or XML assumptions after migration.
- Dialogs and bottom sheets use inconsistent project components when
AppAlertDialog,AppModalBottomSheet, or existing option sheets fit. - Image loading bypasses existing Coil cover/image helpers where cover behavior, cache, SVG, or GIF handling matters.
Clean Architecture Checks
For new screens, expect:
- Compose renders state and emits user actions.
- ViewModel owns state, intent handling, and effect emission.
- Usecases own reusable business actions.
- Repositories mediate data access.
- DAOs remain behind repositories unless adding a boundary would be disproportionate and local project patterns already allow direct use.
For migrated screens, allow pragmatic intermediate code only when:
- It preserves behavior.
- It is isolated behind a clear compatibility boundary.
- It does not spread View-era assumptions into new Compose-first code.
Output Template
Use this shape unless the user asks for another format:
Findings
- [P1] Title
file:line
Impact: ...
Fix: ...
Open Questions
- ...
Notes
- No issues found in ... / Tests not run ...
When using Codex inline review comments, emit one directive per finding with tight line ranges:
::code-comment{title="[P2] Keep route state in ViewModel" body="..." file="/absolute/path/FeatureScreen.kt" start=42 end=45 priority=2 confidence=0.8}
Verification Suggestions
Recommend verification based on the reviewed change:
- Kotlin-only review/fix:
.\gradlew.bat :app:compileAppDebugKotlin. - Resource/XML/manifest impact:
.\gradlew.bat :app:assembleAppDebug. - Navigation or compatibility changes: manually open both
MainActivityroute and any retained legacy AndroidIntententry point. - Behavior-heavy ViewModel changes: add or run focused tests only where the project has a practical test seam.