Integrating Legacy Android Views with Jetpack Compose

When migrating an Android application to Jetpack Compose, it is often necessary to reuse existing XML layouts or custom Views within the new declarative UI. The AndroidView composable facilitates this interoperability by allowing developers to embed standard Android Views directly into a Compose hierarchy.

Previewing Legacy Layouts

To preview an XML layout within a Composable function, you can use the @Preview annotation alongside AndroidView. In the following example, a legacy layout resource is inflated and configured to fill the maximum available space.

@Preview
@Composable
fun LegacyViewPreview() {
    AndroidView(
        factory = { context ->
            LayoutInflater.from(context).inflate(R.layout.item_details_layout, null)
        },
        modifier = Modifier.fillMaxSize()
    )
}

The factory lambda is responsible for creating the View instance. Once the View is created, you can perform further updates using the update parameter. This callback receives the inflated View, allowing you to modify its properties or attach listeners as the Compose state changes.

AndroidView(
    factory = { context ->
        LayoutInflater.from(context).inflate(R.layout.item_details_layout, null)
    },
    update = { view ->
        // Logic to update the View state
        val textView = view.findViewById<TextView>(R.id.statusText)
        textView.text = "Updated State"
    }
)

Embedding Compose in Fragments

Conversely, you may need to introduce Compose UI elements into a traditional Fragment-based architecture. This is achieved by adding a ComposeView to the Fragment's view hierarchy. When dealing with multiple ComposeView instances within a single layout, it is critical to assign unique resource IDs to them.

If unique IDs are not assigned, the system may fail to correctly manage the view lifecycle or restore state, potentially leading to crashes when navigating away from and back to the screen. You should define these IDs in a resource file (e.g., res/values/ids.xml).

<resources>
    <item name="dashboard_compose_a" type="id" />
    <item name="dashboard_compose_b" type="id" />
</resources>

In the Fragment implementation, you can programmatically add these views. Its best practice too set the ViewCompositionStrategy to DisposeOnViewTreeLifecycleDestroyed to ensure the Compose content is properly disposed of when the Fragment's view is destroyed.

class DashboardFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View = LinearLayout(requireContext()).apply {
        orientation = LinearLayout.VERTICAL

        addView(
            createComposeView(R.id.dashboard_compose_a)
        )

        addView(TextView(requireContext()).apply {
            text = "Legacy Header"
        })

        addView(
            createComposeView(R.id.dashboard_compose_b)
        )
    }

    private fun createComposeView(@IdRes id: Int): ComposeView {
        return ComposeView(requireContext()).apply {
            setViewCompositionStrategy(
                ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
            )
            this.id = id
            // Set content here
        }
    }
}

Context Handling Differences

It is important to distinguish between how Context is accessed depending on whether you are working in the View system or Compose.

  • Inside a Fragment (View System): Use the Fragment's method requireContext() to access the context associated with the host Activity.
  • Inside a Composable: Use the LocalContext composable local to retrieve the current Context.
// Inside a Composable function
@Composable
fun MyComposableScreen() {
    val context = LocalContext.current
    // Use context as needed
}

Tags: jetpack-compose Android Interop android-views xml-layouts

Posted on Wed, 09 Sep 2026 16:13:31 +0000 by gatoruss