xml-to-compose-migration

XML to Compose Migration

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "xml-to-compose-migration" with this command: npx skills add new-silvermoon/awesome-android-agent-skills/new-silvermoon-awesome-android-agent-skills-xml-to-compose-migration

XML to Compose Migration

Overview

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.

Workflow

  1. Analyze the XML Layout
  • Identify the root layout type (ConstraintLayout , LinearLayout , FrameLayout , etc.).

  • List all View widgets and their key attributes.

  • Map data binding expressions (@{} ) or view binding references.

  • Identify custom views that need special handling.

  • Note any include , merge , or ViewStub usage.

  1. Plan the Migration
  • Decide: Full rewrite or incremental migration (using ComposeView /AndroidView ).

  • Identify state sources (ViewModel, LiveData, savedInstanceState).

  • List reusable components to extract as separate Composables.

  • Plan navigation integration if using Navigation component.

  1. Convert Layouts

Apply the layout mapping table below to convert each View to its Compose equivalent.

  1. Migrate State
  • Convert LiveData observation to StateFlow collection or observeAsState() .

  • Replace findViewById / ViewBinding with Compose state.

  • Convert click listeners to lambda parameters.

  1. Test and Verify
  • Compare visual output between XML and Compose versions.

  • Test accessibility (content descriptions, touch targets).

  • Verify state preservation across configuration changes.

Layout Mapping Reference

Container Layouts

XML Layout Compose Equivalent Notes

LinearLayout (vertical)

Column

Use Arrangement and Alignment

LinearLayout (horizontal)

Row

Use Arrangement and Alignment

FrameLayout

Box

Children stack on top of each other

ConstraintLayout

ConstraintLayout (Compose) Use createRefs() and constrainAs

RelativeLayout

Box or ConstraintLayout

Prefer Box for simple overlap

ScrollView

Column

  • Modifier.verticalScroll()

Or use LazyColumn for lists

HorizontalScrollView

Row

  • Modifier.horizontalScroll()

Or use LazyRow for lists

RecyclerView

LazyColumn / LazyRow / LazyGrid

Most common migration

ViewPager2

HorizontalPager

From accompanist or Compose Foundation

CoordinatorLayout

Custom + Scaffold

Use TopAppBar with scroll behavior

NestedScrollView

Column

  • Modifier.verticalScroll()

Prefer Lazy variants

Common Widgets

XML Widget Compose Equivalent Notes

TextView

Text

Use style → TextStyle

EditText

TextField / OutlinedTextField

Requires state hoisting

Button

Button

Use onClick lambda

ImageView

Image

Use painterResource() or Coil

ImageButton

IconButton

Use Icon inside

CheckBox

Checkbox

Requires checked

  • onCheckedChange

RadioButton

RadioButton

Use with Row for groups

Switch

Switch

Requires state hoisting

ProgressBar (circular)

CircularProgressIndicator

ProgressBar (horizontal)

LinearProgressIndicator

SeekBar

Slider

Requires state hoisting

Spinner

DropdownMenu

  • ExposedDropdownMenuBox

More complex pattern

CardView

Card

From Material 3

Toolbar

TopAppBar

Use inside Scaffold

BottomNavigationView

NavigationBar

Material 3

FloatingActionButton

FloatingActionButton

Use inside Scaffold

Divider

HorizontalDivider / VerticalDivider

Space

Spacer

Use Modifier.size()

Attribute Mapping

XML Attribute Compose Modifier/Property

android:layout_width="match_parent"

Modifier.fillMaxWidth()

android:layout_height="match_parent"

Modifier.fillMaxHeight()

android:layout_width="wrap_content"

Modifier.wrapContentWidth() (usually implicit)

android:layout_weight

Modifier.weight(1f)

android:padding

Modifier.padding()

android:layout_margin

Modifier.padding() on parent, or use Arrangement.spacedBy()

android:background

Modifier.background()

android:visibility="gone"

Conditional composition (don't emit)

android:visibility="invisible"

Modifier.alpha(0f) (keeps space)

android:clickable

Modifier.clickable { }

android:contentDescription

Modifier.semantics { contentDescription = "" }

android:elevation

Modifier.shadow() or component's elevation param

android:alpha

Modifier.alpha()

android:rotation

Modifier.rotate()

android:scaleX/Y

Modifier.scale()

android:gravity

Alignment parameter or Arrangement

android:layout_gravity

Modifier.align()

Common Patterns

LinearLayout with Weights

<!-- XML --> <LinearLayout android:orientation="horizontal"> <View android:layout_weight="1" /> <View android:layout_weight="2" /> </LinearLayout>

// Compose Row(modifier = Modifier.fillMaxWidth()) { Box(modifier = Modifier.weight(1f)) Box(modifier = Modifier.weight(2f)) }

RecyclerView to LazyColumn

<!-- XML --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" />

// Compose LazyColumn(modifier = Modifier.fillMaxSize()) { items(items, key = { it.id }) { item -> ItemRow(item = item, onClick = { onItemClick(item) }) } }

EditText with Two-Way Binding

<!-- XML with Data Binding --> <EditText android:text="@={viewModel.username}" android:hint="@string/username_hint" />

// Compose val username by viewModel.username.collectAsState()

OutlinedTextField( value = username, onValueChange = { viewModel.updateUsername(it) }, label = { Text(stringResource(R.string.username_hint)) }, modifier = Modifier.fillMaxWidth() )

ConstraintLayout Migration

<!-- XML --> <androidx.constraintlayout.widget.ConstraintLayout> <TextView android:id="@+id/title" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" /> <TextView android:id="@+id/subtitle" app:layout_constraintTop_toBottomOf="@id/title" app:layout_constraintStart_toStartOf="@id/title" /> </androidx.constraintlayout.widget.ConstraintLayout>

// Compose ConstraintLayout(modifier = Modifier.fillMaxWidth()) { val (title, subtitle) = createRefs()

Text(
    text = "Title",
    modifier = Modifier.constrainAs(title) {
        top.linkTo(parent.top)
        start.linkTo(parent.start)
    }
)
Text(
    text = "Subtitle", 
    modifier = Modifier.constrainAs(subtitle) {
        top.linkTo(title.bottom)
        start.linkTo(title.start)
    }
)

}

Include / Merge → Extract Composable

<!-- XML: layout_header.xml --> <merge> <ImageView android:id="@+id/avatar" /> <TextView android:id="@+id/name" /> </merge>

<!-- Usage --> <include layout="@layout/layout_header" />

// Compose: Extract as a reusable Composable @Composable fun HeaderSection( avatarUrl: String, name: String, modifier: Modifier = Modifier ) { Row(modifier = modifier) { AsyncImage(model = avatarUrl, contentDescription = null) Text(text = name) } }

// Usage HeaderSection(avatarUrl = user.avatar, name = user.name)

Incremental Migration (Interop)

Embedding Compose in XML

<!-- In your XML layout --> <androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" android:layout_width="match_parent" android:layout_height="wrap_content" />

// In Fragment/Activity binding.composeView.setContent { MaterialTheme { MyComposable() } }

Embedding XML Views in Compose

// Use AndroidView for Views that don't have Compose equivalents @Composable fun MapViewComposable(modifier: Modifier = Modifier) { AndroidView( factory = { context -> MapView(context).apply { // Initialize the view } }, update = { mapView -> // Update the view when state changes }, modifier = modifier ) }

State Migration

LiveData to Compose

// Before: Observing in Fragment viewModel.uiState.observe(viewLifecycleOwner) { state -> binding.title.text = state.title }

// After: Collecting in Compose @Composable fun MyScreen(viewModel: MyViewModel = hiltViewModel()) { val uiState by viewModel.uiState.collectAsStateWithLifecycle()

Text(text = uiState.title)

}

Click Listeners

// Before: XML + setOnClickListener binding.submitButton.setOnClickListener { viewModel.submit() }

// After: Lambda in Compose Button(onClick = { viewModel.submit() }) { Text("Submit") }

Checklist

  • All layouts converted (no include or merge left)

  • State hoisted properly (no internal mutable state for user input)

  • Click handlers converted to lambdas

  • RecyclerView adapters removed (using LazyColumn/LazyRow)

  • ViewBinding/DataBinding removed

  • Navigation integrated (NavHost or interop)

  • Theming applied (MaterialTheme)

  • Accessibility preserved (content descriptions, touch targets)

  • Preview annotations added for development

  • Old XML files deleted

References

  • Interoperability APIs

  • Migration Strategy

  • Compose and Views side by side

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

gradle-build-performance

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

compose-ui

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

kotlin-concurrency-expert

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

android-testing

No summary provided by upstream source.

Repository SourceNeeds Review