$npx -y skills add hanamizuki/solopreneur --skill xml-to-compose-migrationConvert Android XML layouts to Jetpack Compose. Use when asked to migrate Views to Compose, convert XML to Composables, or modernize UI from View system to Compose.
| 1 | # XML to Compose Migration |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | Systematically convert Android XML layouts to idiomatic Jetpack Compose, preserving functionality while embracing Compose patterns. This skill covers layout mapping, state migration, and incremental adoption strategies. |
| 6 | |
| 7 | ## Workflow |
| 8 | |
| 9 | ### 1. Analyze the XML Layout |
| 10 | |
| 11 | - Identify the root layout type (`ConstraintLayout`, `LinearLayout`, `FrameLayout`, etc.). |
| 12 | - List all View widgets and their key attributes. |
| 13 | - Map data binding expressions (`@{}`) or view binding references. |
| 14 | - Identify custom views that need special handling. |
| 15 | - Note any `include`, `merge`, or `ViewStub` usage. |
| 16 | |
| 17 | ### 2. Plan the Migration |
| 18 | |
| 19 | - Decide: **Full rewrite** or **incremental migration** (using `ComposeView`/`AndroidView`). |
| 20 | - Identify state sources (ViewModel, LiveData, savedInstanceState). |
| 21 | - List reusable components to extract as separate Composables. |
| 22 | - Plan navigation integration if using Navigation component. |
| 23 | |
| 24 | ### 3. Convert Layouts |
| 25 | |
| 26 | Apply the layout mapping table below to convert each View to its Compose equivalent. |
| 27 | |
| 28 | ### 4. Migrate State |
| 29 | |
| 30 | - Convert `LiveData` observation to `StateFlow` collection or `observeAsState()`. |
| 31 | - Replace `findViewById` / ViewBinding with Compose state. |
| 32 | - Convert click listeners to lambda parameters. |
| 33 | |
| 34 | ### 5. Test and Verify |
| 35 | |
| 36 | - Compare visual output between XML and Compose versions. |
| 37 | - Test accessibility (content descriptions, touch targets). |
| 38 | - Verify state preservation across configuration changes. |
| 39 | |
| 40 | --- |
| 41 | |
| 42 | ## Layout Mapping Reference |
| 43 | |
| 44 | ### Container Layouts |
| 45 | |
| 46 | | XML Layout | Compose Equivalent | Notes | |
| 47 | |------------|-------------------|-------| |
| 48 | | `LinearLayout (vertical)` | `Column` | Use `Arrangement` and `Alignment` | |
| 49 | | `LinearLayout (horizontal)` | `Row` | Use `Arrangement` and `Alignment` | |
| 50 | | `FrameLayout` | `Box` | Children stack on top of each other | |
| 51 | | `ConstraintLayout` | `ConstraintLayout` (Compose) | Use `createRefs()` and `constrainAs` | |
| 52 | | `RelativeLayout` | `Box` or `ConstraintLayout` | Prefer Box for simple overlap | |
| 53 | | `ScrollView` | `Column` + `Modifier.verticalScroll()` | Or use `LazyColumn` for lists | |
| 54 | | `HorizontalScrollView` | `Row` + `Modifier.horizontalScroll()` | Or use `LazyRow` for lists | |
| 55 | | `RecyclerView` | `LazyColumn` / `LazyRow` / `LazyGrid` | Most common migration | |
| 56 | | `ViewPager2` | `HorizontalPager` | From accompanist or Compose Foundation | |
| 57 | | `CoordinatorLayout` | Custom + `Scaffold` | Use `TopAppBar` with scroll behavior | |
| 58 | | `NestedScrollView` | `Column` + `Modifier.verticalScroll()` | Prefer Lazy variants | |
| 59 | |
| 60 | ### Common Widgets |
| 61 | |
| 62 | | XML Widget | Compose Equivalent | Notes | |
| 63 | |------------|-------------------|-------| |
| 64 | | `TextView` | `Text` | Use `style` → `TextStyle` | |
| 65 | | `EditText` | `TextField` / `OutlinedTextField` | Requires state hoisting | |
| 66 | | `Button` | `Button` | Use `onClick` lambda | |
| 67 | | `ImageView` | `Image` | Use `painterResource()` or Coil | |
| 68 | | `ImageButton` | `IconButton` | Use `Icon` inside | |
| 69 | | `CheckBox` | `Checkbox` | Requires `checked` + `onCheckedChange` | |
| 70 | | `RadioButton` | `RadioButton` | Use with `Row` for groups | |
| 71 | | `Switch` | `Switch` | Requires state hoisting | |
| 72 | | `ProgressBar (circular)` | `CircularProgressIndicator` | | |
| 73 | | `ProgressBar (horizontal)` | `LinearProgressIndicator` | | |
| 74 | | `SeekBar` | `Slider` | Requires state hoisting | |
| 75 | | `Spinner` | `DropdownMenu` + `ExposedDropdownMenuBox` | More complex pattern | |
| 76 | | `CardView` | `Card` | From Material 3 | |
| 77 | | `Toolbar` | `TopAppBar` | Use inside `Scaffold` | |
| 78 | | `BottomNavigationView` | `NavigationBar` | Material 3 | |
| 79 | | `FloatingActionButton` | `FloatingActionButton` | Use inside `Scaffold` | |
| 80 | | `Divider` | `HorizontalDivider` / `VerticalDivider` | | |
| 81 | | `Space` | `Spacer` | Use `Modifier.size()` | |
| 82 | |
| 83 | ### Attribute Mapping |
| 84 | |
| 85 | | XML Attribute | Compose Modifier/Property | |
| 86 | |---------------|--------------------------| |
| 87 | | `android:layout_width="match_parent"` | `Modifier.fillMaxWidth()` | |
| 88 | | `android:layout_height="match_parent"` | `Modifier.fillMaxHeight()` | |
| 89 | | `android:layout_width="wrap_content"` | `Modifier.wrapContentWidth()` (usually implicit) | |
| 90 | | `android:layout_weight` | `Modifier.weight(1f)` | |
| 91 | | `android:padding` | `Modifier.padding()` | |
| 92 | | `android:layout_margin` | `Modifier.padding()` on parent, or use `Arrangement.spacedBy()` | |
| 93 | | `android:background` | `Modifier.background()` | |
| 94 | | `android:visibility="gone"` | Conditional composition (don't emit) | |
| 95 | | `android:visibility="invisible"` | `Modifier.alpha(0f)` (keeps space) | |
| 96 | | `android:clickable` | `Modifier.clickable { }` | |
| 97 | | `android:contentDescription` | `Modifier.semantics { contentDescription = "" }` | |
| 98 | | `android:elevation` | `Modifier.shadow()` or component's `elevation` param | |
| 99 | | `android:alpha` | `Modifier.alpha()` | |
| 100 | | `android:rotation` | `Modifier.rotate()` | |
| 101 | | `android:scaleX/Y` | `Modifier.scale()` | |
| 102 | | `android:gravity` | `Al |