| name | legado-compose-migration |
| description | Guide Legado Android UI migration from XML/View/RecyclerView/DialogFragment screens to Jetpack Compose and Material 3, and guide new Compose-first screens using standard Android architecture. Use when creating, migrating, rewriting, reviewing, or planning a Legado screen, Activity, Fragment, dialog, adapter, navigation destination, or settings page, especially when MainActivity navigation, MVI/UDF, StateFlow/SharedFlow, Koin ViewModels, domain/usecase boundaries, edge-to-edge insets, predictive back, or existing Compose component conventions matter. |
Legado Compose Migration
Overview
Migrate one UI surface at a time, or create one new Compose destination at a time. Preserve behavior first for migrations; for newly created screens, prefer standard modern Android architecture over mixed legacy patterns. Use the project's existing MainActivity navigation, BaseComposeActivity compatibility hosts, *Screen, *Contract, *ViewModel, StateFlow, SharedFlow, Koin, theme, and widget patterns.
Before editing, inspect the target View implementation if one exists, MainActivity navigation when adding a destination, and at least two nearby migrated Compose screens. For concrete project patterns, including current Compose state/performance rules, read references/project-patterns.md.
Workflow
-
State assumptions and success criteria.
- Name the exact screen or destination being created/migrated.
- For migrations, define behavior that must remain unchanged: inputs, result codes, navigation, menu actions, dialogs/sheets, list selection, refresh, persistence, and event bus behavior.
- For new screens, define the route owner, UI state, events/effects, domain/usecase boundary, and verification target.
- If the target mixes UI and business logic heavily, keep the migration surgical and defer deeper domain cleanup unless needed.
-
Map the old surface.
- For migrations, read the Activity/Fragment, XML layouts, adapters, menu XML, dialogs, result launchers, and ViewModel.
- For new screens, read
MainActivity, nearby route screens, the relevant ViewModel/usecase/repository patterns, and shared UI components.
- List UI state, user intents, one-shot effects, and external side effects.
- Identify reusable Compose components under
ui/widget/components before creating new components.
-
Choose the minimal migration shape.
- For new Compose-first screens, add a
MainActivity navigation destination instead of creating a standalone Activity.
- Use
BaseComposeActivity for full-screen Activity migrations only when a legacy Activity must remain as an entry point.
- Keep existing Activity Result APIs,
Intent extras, permission flows, and Android framework calls in the host/compatibility Activity.
- If an unreworked View screen still starts the migrated screen with
Intent, keep the old Activity only as a compatibility host that parses legacy extras and delegates to the Compose screen or MainActivity route boundary.
- Put renderable state in
UiState, user actions in Intent, and one-shot navigation/framework work in Effect.
- For new screens, use standard Android/Compose architecture: UDF/MVI-style state hoisting, lifecycle-aware Flow collection, ViewModel-owned state, repository/usecase boundaries, and UI free of business logic.
- Ensure the new screen handles edge-to-edge correctly: use
Scaffold (which respects WindowInsets automatically) or apply Modifier.windowInsetsPadding(WindowInsets.safeDrawing) on the outermost container. Do not carry over fitsSystemWindows / manual padding patterns from XML.
- Use mixed legacy patterns only as an integration boundary for unreworked View screens or existing framework contracts.
- Use existing repositories/usecases when they already fit; introduce new domain/usecase classes when a new screen needs clean business boundaries or when a migration would otherwise duplicate or entangle business logic.
-
Implement by layers.
- Create or update
FeatureContract.kt first for UiState, Intent, Effect, dialog/sheet models, and menu action enums.
- Update
FeatureViewModel.kt to expose uiState: StateFlow<UiState> and effects: SharedFlow<Effect>, with a single onIntent(...) entry point unless the existing feature has a simpler established pattern.
- Create
FeatureScreen.kt as a stateless route-level composable: state, callbacks, and onIntent.
- Wire new Compose destinations through
MainActivity route handling; update a retained Activity only when legacy Intent compatibility is required.
- Collect state with
collectAsStateWithLifecycle() and collect effects in LaunchedEffect(Unit) from the route or compatibility host.
- Register new ViewModels in
di/appModule.kt with viewModelOf(::FeatureViewModel) unless parameters require viewModel { ... }.
-
Remove only obsolete migration artifacts.
- Delete XML layouts, adapters, menu resources, binding fields, and imports only when the migrated screen no longer references them.
- Do not refactor unrelated View screens or shared utilities.
-
Verify.
- Prefer the smallest Gradle check that compiles the touched app code, usually
.\gradlew.bat :app:compileAppDebugKotlin.
- If resources, manifests, or XML deletion are involved, run
.\gradlew.bat :app:assembleAppDebug when feasible.
- For behavior-heavy changes, add or update focused tests only where the project already has a practical test seam.
Boundaries
- Keep Compose functions side-effect-light. Use
LaunchedEffect for collecting effects and use callbacks for user actions.
- Do not pass
Activity, View, binding objects, or mutable domain entities deep into composables unless an existing local pattern requires it.
- Prefer project theme/components:
LegadoTheme, AppScaffold, ListScaffold, AppAlertDialog, AppModalBottomSheet, RoundDropdownMenu, top bar helpers, setting items, cover components, and list utilities.
- Prefer
StateFlow/SharedFlow over LiveData for newly migrated Compose surfaces.
- Annotate
UiState data classes and UI-facing model wrappers with @Stable when they hold collections or entity data passed to composables. Kotlin 2.x strong skipping is on by default, but explicit @Stable provides the strongest compiler guarantee and helps document contract boundaries.
- When using
FeatureIntent for MVI user actions, distinguish it from Android Intent extras and launch APIs in names, comments, and explanations where both appear.
- For new Compose-first screens, do not copy View-era shortcuts such as UI logic in Activity/Fragment, direct binding-like mutable UI state, adapter-owned state, or Activity-context business operations.
- Keep one-off Android actions out of
UiState: navigation, file opening, clipboard, dialogs implemented as Android DialogFragments, result launchers, permission requests, and callbacks that require host context should be Effects handled by MainActivity route handling or a compatibility Activity.
- Prefer
MainActivity navigation for new Compose destinations. Treat standalone Activities for migrated screens as legacy entry points only when existing View code still depends on Intent navigation.
- For edge-to-edge: this project targets SDK 37, so edge-to-edge is enforced on Android 15+. Use Material 3
Scaffold insets or WindowInsets.safeDrawing / safeContent padding; ensure migrated screens draw behind system bars via Modifier.windowInsetsPadding() or top-level scaffold padding rather than manual hardcoded offsets. Migrated screens that relied on fitsSystemWindows in XML must be updated.
- For predictive back: New Compose destinations should work with the predictive back gesture (enabled by default in Navigation 3). When a screen requires back confirmation (unsaved changes, selection mode), use
BackHandler to intercept and route through FeatureIntent.BackPressed.
- Keep existing Chinese string resources and localization behavior; add strings to resources when user-facing text is new.
Reference
Read references/project-patterns.md when implementing or reviewing a migration. It contains project-specific examples, file placement rules, state/effect conventions, and verification commands.