XML to Jetpack Compose migrator and verifier
Use this skill when converting Android XML layouts into Jetpack Compose, or when auditing and aligning Compose screens against legacy XML layouts.
It covers full screens (Activities and Fragments), modal layouts (Dialogs and Bottom Sheets), repeated items (RecyclerView list items), and modular partials (includes and custom compound views).
1. Plan-first workflow for batch or project-wide migrations
When asked to migrate all XML layouts, a large group of screens, or an entire module, prepare a plan first before modifying or deleting any existing code.
Step 1: Inventory and dependency audit
Scan all layout directories across the app and modules:
res/layoutres/layout-land- Module layout folders (such as shared UI modules)
Group each XML file by component type:
- Activities (
activity_*.xml) - Fragments (
fragment_*.xml) - Dialogs and Bottom Sheets (
dialog_*.xml,bottom_sheet_*.xml) - List/Grid Items (
item_*.xml,list_item_*.xml) - Partials and Compounds (
view_*.xml,layout_*.xml,header_*.xml)
Step 2: Document the migration plan in <workspace>/docs/
Save the plan as a Markdown file in the project's documentation folder:
<workspace>/docs/xml_to_compose_migration_plan.md
The document must detail:
- Current inventory table (layout file, host class, migration status, target composable path).
- Wave order (order of execution: items and dialogs first, then fragments, then host activities).
- Shared state and ViewModel bindings needed per screen.
- Theme and typography tokens mapped from XML attributes.
- Risk factors (such as third-party View dependencies, custom drawables, or legacy animations).
Step 3: User approval
Present the plan document path to the user and request confirmation before touching code. Once approved, proceed wave by wave.
Step 4: Subagent delegation (optional for parallel tasks)
For large migrations with independent screens (for example, five separate activity layouts that share no state), launch subagents using invoke_subagent:
- Assign one subagent per independent screen or feature module.
- Provide each subagent with the exact target file path, XML reference path, and verification instructions.
- Aggregate build results once all subagents finish.
2. Single layout migration workflow
When migrating or verifying a single layout file or screen, follow this four-step sequence:
- Analyze the XML layout, associated themes, attributes, and host component (Activity, Fragment, Adapter, or Dialog).
- Find or create the target Composable file following project conventions.
- Translate view elements, styling, layout constraints, and interactions line by line.
- Run Kotlin build verification and fix any compile or lint errors.
3. Deep XML analysis checklist
Before writing any Compose code, inspect these core areas:
View to Compose element mappings
| XML view | Jetpack Compose equivalent | Notes |
|---|---|---|
MaterialToolbar / Toolbar |
TopAppBar, MediumTopAppBar, or CenterAlignedTopAppBar |
Place inside Scaffold topBar slot |
AppBarLayout + CollapsingToolbarLayout |
LargeTopAppBar or MediumTopAppBar with TopAppBarDefaults.exitUntilCollapsedScrollBehavior() |
Connect to Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) |
ScrollView / NestedScrollView |
Column(modifier = Modifier.verticalScroll(rememberScrollState())) |
Remember to apply safe padding |
HorizontalScrollView |
Row(modifier = Modifier.horizontalScroll(rememberScrollState())) |
|
RecyclerView (vertical) |
LazyColumn |
Pass list padding to contentPadding |
RecyclerView (horizontal) |
LazyRow |
Pass list padding to contentPadding |
RecyclerView (grid) |
LazyVerticalGrid(columns = GridCells.Fixed(n)) |
|
ConstraintLayout |
Nested Column and Row layouts, or ConstraintLayout (from Compose library) |
Prefer standard Rows/Columns for readability unless complex layout chains exist |
FrameLayout |
Box |
Use Alignment modifiers for child positions |
LinearLayout (vertical) |
Column |
|
LinearLayout (horizontal) |
Row |
|
MaterialCardView / CardView |
Card, OutlinedCard, or ElevatedCard |
Match XML cardElevation and strokeWidth |
MaterialButton (Filled) |
Button |
|
MaterialButton (Tonal) |
FilledTonalButton |
Often used for secondary/neutral actions |
MaterialButton (Outlined) |
OutlinedButton |
|
MaterialButton (Text) |
TextButton |
Standard for dialog actions |
FloatingActionButton |
FloatingActionButton |
Place inside Scaffold floatingActionButton slot |
ExtendedFloatingActionButton |
ExtendedFloatingActionButton |
|
TextView |
Text |
Wrap in SelectionContainer if selectable |
EditText / TextInputEditText |
OutlinedTextField or TextField |
Wire value and onValueChange state |
ImageView |
Image or Icon |
Use Icon for tinted vector drawables |
ProgressBar (circular) |
CircularProgressIndicator |
|
ProgressBar (horizontal) |
LinearProgressIndicator |
|
Switch / MaterialSwitch |
Switch |
|
CheckBox |
Checkbox |
|
RadioButton / RadioGroup |
RadioButton within a selectable Row or Column |
|
TabLayout |
PrimaryTabRow or SecondaryTabRow with Tab items |
|
ViewPager2 |
HorizontalPager(state = rememberPagerState(...)) |
|
SwipeRefreshLayout |
PullToRefreshBox |
|
include tag |
Separate reusable Composable function | Keeps files modular |
Styling and theme tokens
Never hardcode hex values when color tokens exist in your theme:
?attr/colorPrimarytranslates toMaterialTheme.colorScheme.primary?attr/colorSurfacetranslates toMaterialTheme.colorScheme.surface?attr/colorSurfaceContainerHightranslates toMaterialTheme.colorScheme.surfaceContainerHigh?attr/colorOnErrorContainertranslates toMaterialTheme.colorScheme.onErrorContainer- Custom project theme extensions translate to corresponding
MaterialThemeextension properties - Ripple effects and highlights (
?attr/colorControlHighlight) use default Material 3 interactions
Typography and strings
- Map
android:textAppearance="?attr/textAppearanceTitleLarge"toMaterialTheme.typography.titleLarge. - Map
android:textStyle="bold"tofontWeight = FontWeight.Bold. - Map
android:fontFamilyto the project's configured font family. - Always load strings using
stringResource(R.string.your_string_name)instead of hardcoding English text.
Interactive states and accessibility
android:textIsSelectable="true": Wrap theTextcomposable insideSelectionContainer { Text(...) }. Crucial for stack traces, error reports, and IDs.android:contentDescription: Always supply a descriptive string ornullfor decorative elements.android:enabled="false": Passenabled = falseto the composable.android:maxLines="N": PassmaxLines = N, overflow = TextOverflow.Ellipsis.
4. Handling different layout types
Type A: Full screen Activity layouts (activity_*.xml)
- Add
enableEdgeToEdge()in the Activity'sonCreate()beforesetContent. - Wrap content in
Scaffold. - Pass
innerPaddingfromScaffoldto scroll containers usingcontentPadding = innerPaddingor.padding(innerPadding).consumeWindowInsets(innerPadding)to prevent content from hiding beneath navigation and status bars.
Type B: Fragment layouts (fragment_*.xml)
- Create the screen composable
Screen.kt. - In
Fragment.onCreateView, returnComposeView:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
YourFragmentScreen(...)
}
}
}
Type C: Bottom sheets and dialogs
- Map
BottomSheetDialogFragmenttoModalBottomSheet(onDismissRequest = { ... }). - Map
AlertDialogtoAlertDialogor customDialogcomposable. - Use
FilledTonalButtonorTextButtonfor dismiss and confirm actions.
Type D: RecyclerView item layouts (item_*.xml, list_item_*.xml)
- Convert the XML into a standalone item composable (for example
UserListItem(...)). - Supply clear callback parameters for user interactions like
onClick: (Item) -> UnitandonLongClick: (() -> Unit)? = null. - Keep the item composable stateless. Pass data through model objects.
Type E: Compound views and included layouts (view_*.xml, layout_*.xml)
- Convert each
<include layout="@layout/header_view" />into an independent composable function. - Group related UI state into plain data classes if more than four parameters are passed.
5. Detection and target naming rules
Search existing files before generating new ones:
activity_profile.xmlmaps toProfileScreen.ktinui/profile/activity_manage_project.xmlmaps toManageProjectScreen.ktinui/project/item_product.xmlmaps toProductItem.ktorProductListItem.ktinui/store/components/dialog_alert_warning.xmlmaps toWarningAlertDialog.ktinui/components/
When in doubt, search the codebase with git or directory tools to verify whether an existing composable already serves the screen.
6. Verification and build checks
After making code changes, verify your work:
./gradlew :app:compileDebugKotlin
Resolve any compile errors immediately, including:
- Deprecated composables or modifiers (such as
menuAnchor()orTabRow). - Missing extended Material icons (ensure
material-icons-extendedis imported if needed). - Unconsumed window insets causing duplicate padding.