Migrate Xml Views To Jetpack Compose
Android (Google)OfficialCreated by Google LLCDistributed by Android (Google)
- Skill
- Workflow
- Software development
- Apache-2.0
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a systematic methodology for converting Android XML Views into declarative Jetpack Compose components. It emphasizes maintaining visual parity and functional integrity through a rigorous process that includes project analysis, dependency setup, theming alignment, and validation. The workflow is designed for incremental adoption, ensuring that legacy XML and new Compose code can coexist through interoperability APIs while preparing the codebase for a full transition to Compose.
What it does
- Analyzes existing XML layouts and project infrastructure for migration readiness.
- Guides the setup of Compose dependencies and compiler configurations.
- Facilitates the migration of XML themes to Material 3 Compose theming.
- Provides a structured approach to replacing XML View usages with Compose components.
- Ensures visual parity through automated screenshot testing and Compose Previews.
- Manages the safe removal of legacy XML files and associated resources.
When to use it
- When modernizing an Android application's UI layer from XML to Jetpack Compose.
- When you need to perform an incremental migration of specific UI components.
- When you need to ensure visual consistency between legacy XML and new Compose code.
- When integrating new Compose features into an existing View-based Android project.
Advantages
- Reduces risk by following a validated, step-by-step migration methodology.
- Maintains project stability through interoperability best practices.
- Ensures visual fidelity using baseline screenshot comparisons.
- Promotes clean code by systematically removing redundant legacy XML resources.
Usage examples
- “Help me migrate my login_screen.xml to Jetpack Compose.”
- The agent will initiate the 10-step workflow, starting with an analysis of the login_screen.xml structure and project dependencies to create a migration plan.
- “How do I handle theming when moving from XML to Compose?”
- The agent will guide you through mapping XML color attributes and styles to Material 3 semantic roles within a Compose MaterialTheme.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/android/migrate-xml-views-to-jetpack-compose/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip migrate-xml-views-to-jetpack-compose.zip -d .agents/skills
Documentation by Google LLC
This skill guides through the process of migrating an existing Android XML View to Jetpack Compose. It performs a stable, safe and visually consistent transition by following a structured, 10-step methodology. This skill migrates UI (XML to Jetpack Compose) only.
Objective
To systematically convert a single legacy XML layout into modern, declarative Jetpack Compose UI while maintaining pixel-perfect visual parity and functional integrity.
Summary of the 10-step migration process
Identify the optimal XML candidate for migration
Analyze the project and layout
Create a plan
Capture the XML View UI
Set up Compose dependencies and compiler
Set up Compose theming
Migrate the XML layout to Compose
Validate the migration
Replace usages
XML code removal
Detailed steps
Step 1: Identify the optimal XML candidate for migration
If the user has explicitly specified a target XML layout, proceed to Step 2. Otherwise, analyze the codebase to identify the best candidate for migration by following the logic in references/identify-optimal-xml-candidate.md.
Step 2: Analyze the project and layout
Analyze the identified XML View's structure, hierarchy, and implementation details. Use references/analysis-of-the-project-and-layout.md to guide your technical audit of the layout and surrounding project context.
Step 3: Create a plan
Using the outputs and analysis done in the Step 1 and 2, generate a step-by-step plan for the migration. If you support user interaction, present to the user and ask for approval before proceeding. If user interaction is not supported, proceed to Step 4 following the generated plan.
Step 4: Capture the XML View UI
IF you support user interaction, ask the user to upload a screenshot of the XML View UI or provide an absolute path to a file. Use this image as a visual reference for the layout migration in Step 7. ELSE IF you are able to run an Android emulator, locate an existing screenshot test for the XML candidate. If none exists, create one using the existing project testing framework. If no framework exists, use UI Automator or Espresso to create a screenshot test with minimum required setup. Run the test and take a baseline screenshot of the XML UI. ELSE proceed to Step 5.
Step 5: Set up Compose dependencies and compiler
Check build.gradle or libs.versions.toml for Compose dependencies and
compiler setup. If missing, use
Setup Compose Dependencies and Compiler.
Run a sync to ensure dependencies resolve without errors.
Step 6: Set up Compose theming
If the project already has Compose theming set up, proceed to Step 7. If Compose theming is missing, initialize it. For Material-based projects, follow Material 3 migration guidelines. For custom design systems, apply expert judgment to migrate XML theming and match existing styles. Constraints: Do not migrate the entire theme. Implement only the minimum theming required for the specific XML candidate. Maintain original XML themes for interoperability. Maintain existing project code conventions, patterns, names and values.
Step 7: Migrate the XML View to Compose
Convert the XML candidate to Jetpack Compose code, referencing references/xml-layout-migration.md and the image from Step 4. You must include a Compose Preview for the newly created composable to facilitate visual verification.
Step 8: Replace usages
Replace the usages of the migrated XML layout to use the new Compose component.
To add Compose in Views, use Compose in Views.
To add Views in Compose, use Views in Compose.
Step 9: Validate the migration
Compare the baseline screenshot image from Step 4 with the rendered Compose Preview of the new composable. Ignore string content; focus on layout and styling. Iterate on the Compose code until visual parity is achieved. Once verified, write a Compose UI test for the new composable.
Step 10: XML code removal
Delete the migrated XML file and its associated legacy tests. Caution: Only remove code and resources that are not referenced by other parts of the project.
Files
- ATTRIBUTION.mdAdded by AutoGineer1.0 KiB
- LICENSELicence file added by AutoGineer11.1 KiB
- SKILL.md5.3 KiB
- references/analysis-of-the-project-and-layout.md3.1 KiB
- references/android/develop/ui/compose/designsystems/migrate-xml-theme-to-compose.md8.1 KiB
- references/android/develop/ui/compose/migrate/interoperability-apis/compose-in-views.md12.0 KiB
- references/android/develop/ui/compose/migrate/interoperability-apis/views-in-compose.md12.2 KiB
- references/android/develop/ui/compose/setup-compose-dependencies-and-compiler.md7.3 KiB
- references/identify-optimal-xml-candidate.md1.6 KiB
- references/xml-layout-migration.md3.5 KiB
references/android/develop/ui/compose/migrate/interoperability-apis/views-in-compose.md
You can include an Android View hierarchy in a Compose UI. This approach is
particularly useful if you want to use UI elements that are not yet available in
Compose, like
AdView.
This approach also lets you reuse custom views you may have designed.
[!NOTE] Note: Use
AndroidViewas a wrapper only for missing SDK components without Compose support. Rewrite your custom Views in Compose wherever possible, starting the migration with the simplest custom Views and scaling to more complex ones.
<br />
To include a view element or hierarchy, use the AndroidView
composable. AndroidView is passed a lambda that returns a
View. AndroidView also provides an update
callback that is called when the view is inflated. The AndroidView recomposes
whenever a State read within the callback changes. AndroidView, like many
other built-in composables, takes a Modifier parameter that can be used, for
example, to set its position in the parent composable.
@Composable
fun CustomView() {
var selectedItem by remember { mutableIntStateOf(0) }
// Adds view to Compose
AndroidView(
modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
factory = { context ->
// Creates view
MyView(context).apply {
// Sets up listeners for View -> Compose communication
setOnClickListener {
selectedItem = 1
}
}
},
update = { view ->
// View's been inflated or state read in this block has been updated
// Add logic here if necessary
// As selectedItem is read here, AndroidView will recompose
// whenever the state changes
// Example of Compose -> View communication
view.selectedItem = selectedItem
}
)
}
@Composable
fun ContentExample() {
Column(Modifier.fillMaxSize()) {
Text("Look at this CustomView!")
CustomView()
}
}<br />
[!NOTE] Note: Prefer to construct a View in the
AndroidViewfactorylambda instead of usingrememberto hold a View reference outside ofAndroidView.
AndroidView with view binding
To embed an XML layout, use the
AndroidViewBinding
API, which is provided by the androidx.compose.ui:ui-viewbinding library. To
do this, your project must enable view binding.
[!NOTE] Note: For Compose-only apps, don't use
AndroidViewBindingto inflate full screen-level XML layouts, and instead use it only for smaller, legacy XML layouts during the incremental migration process.
@Composable
fun AndroidViewBindingExample() {
AndroidViewBinding(ExampleLayoutBinding::inflate) {
exampleView.setBackgroundColor(Color.GRAY)
}
}<br />
AndroidView in Lazy lists
If you are using an AndroidView in a Lazy list (LazyColumn, LazyRow,
Pager, etc.), consider using the AndroidView
overload introduced in version 1.4.0-rc01. This overload allows Compose to reuse
the underlying View instance when the containing composition is reused as is
the case for Lazy lists.
This overload of AndroidView adds 2 additional parameters:
onReset- A callback invoked to signal that theViewis about to be reused. This must be non-null to enable View reuse.onRelease(optional) - A callback invoked to signal that theViewhas exited the composition and will not be reused again.
@Composable
fun AndroidViewInLazyList() {
LazyColumn {
items(100) { index ->
AndroidView(
modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
factory = { context ->
MyView(context)
},
update = { view ->
view.selectedItem = index
},
onReset = { view ->
view.clear()
}
)
}
}
}<br />
Fragments in Compose (Transitionary Step)
Use the AndroidFragment composable to add a Fragment in Compose.
AndroidFragment has fragment-specific handling such as removing the
fragment when the composable leaves the composition.
[!NOTE] Note: Wrap existing Fragments in Compose only during incremental migration. For Compose-only apps, do not use Fragments and instead use the recommended Compose-only architecture with a single Activity and latest navigation libraries, like Navigation 3.
To include a fragment, use the AndroidFragment
composable. You pass a Fragment class to AndroidFragment, which then adds
an instance of that class directly into the composition. AndroidFragment also
provides a fragmentState object to create the AndroidFragment with a given
state, arguments to pass into the new fragment, and an onUpdate callback
that provides the fragment from the composition. Like many
other built-in composables, AndroidFragment accepts a Modifier parameter
that you can use, for
example, to set its position in the parent composable.
Call AndroidFragment in Compose as follows:
@Composable
fun FragmentInComposeExample() {
AndroidFragment<MyFragment>()
}<br />
Cap Fragment lifecycle state
In Fragment 1.9.0 and higher, you can use the maxLifecycle
parameter to cap the lifecycle state of the embedded Fragment.
For example, in a HorizontalPager, you can dynamically set maxLifecycle
to RESUMED only when the corresponding page is active, keeping offscreen
pages capped at STARTED. When the user navigates to another page, the
previous page's fragment cap becomes STARTED. This pauses or cancels workloads
scoped to RESUMED (such as coroutines running in
repeatOnLifecycle(RESUMED)), preventing offscreen pages from performing
foreground-only work while keeping their UI initialized at STARTED:
HorizontalPager(state = pagerState) { page ->
// Dynamically cap the lifecycle state based on whether the page is selected
val maxLifecycle = if (pagerState.settledPage == page) {
Lifecycle.State.RESUMED
} else {
Lifecycle.State.STARTED
}
when (page) {
0 -> AndroidFragment<HomeFragment>(maxLifecycle = maxLifecycle)
1 -> AndroidFragment<LibraryFragment>(maxLifecycle = maxLifecycle)
/* Other pages and corresponding fragments */
}
}<br />
Calling the Android framework from Compose
Compose operates within the Android framework classes. For example, it's hosted
on Android View classes, like Activity or Fragment, and might use Android
framework classes like the Context, system resources,
Service, or BroadcastReceiver.
To learn more about system resources, see Resources in Compose.
Composition Locals
CompositionLocal
classes allow passing data implicitly through composable functions. They're
usually provided with a value in a certain node of the UI tree. That value can
be used by its composable descendants without declaring the CompositionLocal
as a parameter in the composable function.
CompositionLocal is used to propagate values for Android framework types in
Compose such as Context, Configuration or the View in which the Compose
code is hosted with the corresponding
LocalContext,
LocalConfiguration,
or
LocalView.
Note that CompositionLocal classes are prefixed with Local for better
discoverability with auto-complete in the IDE.
Access the current value of a CompositionLocal by using its current
property. For example, the code below shows a toast message by providing
LocalContext.current into the Toast.makeToast method.
@Composable
fun ToastGreetingButton(greeting: String) {
val context = LocalContext.current
Button(onClick = {
Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show()
}) {
Text("Greet")
}
}<br />
Broadcast receivers
To showcase CompositionLocal and side
effects, if a
BroadcastReceiver needs to be registered from
a composable function, use of LocalContext to use the current context, and
rememberUpdatedState and DisposableEffect side effects.
@Composable
fun SystemBroadcastReceiver(
systemAction: String,
onSystemEvent: (intent: Intent?) -> Unit
) {
// Grab the current context in this part of the UI tree
val context = LocalContext.current
// Safely use the latest onSystemEvent lambda passed to the function
val currentOnSystemEvent by rememberUpdatedState(onSystemEvent)
// If either context or systemAction changes, unregister and register again
DisposableEffect(context, systemAction) {
val intentFilter = IntentFilter(systemAction)
val broadcast = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
currentOnSystemEvent(intent)
}
}
context.registerReceiver(broadcast, intentFilter)
// When the effect leaves the Composition, remove the callback
onDispose {
context.unregisterReceiver(broadcast)
}
}
}
@Composable
fun HomeScreen() {
SystemBroadcastReceiver(Intent.ACTION_BATTERY_CHANGED) { batteryStatus ->
val isCharging = /* Get from batteryStatus ... */ true
/* Do something if the device is charging */
}
/* Rest of the HomeScreen */
}<br />
Other interactions
If there isn't a utility defined for the interaction you need, the best practice is to follow the general Compose guideline, data flows down, events flow up (discussed at more length in Thinking in Compose). For example, this composable launches a different activity:
class OtherInteractionsActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// get data from savedInstanceState
setContent {
MaterialTheme {
ExampleComposable(data, onButtonClick = {
startActivity(Intent(this, MyActivity::class.java))
})
}
}
}
}
@Composable
fun ExampleComposable(data: DataExample, onButtonClick: () -> Unit) {
Button(onClick = onButtonClick) {
Text(data.title)
}
}<br />
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Google LLC
- Publisher
- Android (Google) (Official)
- Distributed by
- Android (Google)
- Source
- android/skills · jetpack-compose/migration/migrate-xml-views-to-jetpack-compose · commit bf68e2d
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.txt)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Google LLC or Android (Google).
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download migrate-xml-views-to-jetpack-compose.zip