Using Compose in Views

You can add Compose-based UI into an existing app that uses a View-based design.

To create a new, entirely Compose-based screen, have your activity call the setContent() method, and pass whatever composable functions you like.

class ExampleActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent { // In here, we can call composables!
            MaterialTheme {
                Greeting(name = "compose")
            }
        }
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

This code looks just like what you'd find in a Compose-only app.

ViewCompositionStrategy for ComposeView

ViewCompositionStrategy defines when the Composition should be disposed. The default, ViewCompositionStrategy.Default, disposes the Composition when the underlying ComposeView detaches from the window, unless it is part of a pooling container such as a RecyclerView. In a single-Activity Compose-only app, this default behavior is what you would want, however, if you are incrementally adding Compose in your codebase, this behavior may cause state loss in some scenarios.

To change the ViewCompositionStrategy, call the setViewCompositionStrategy() method and provide a different strategy.

The table below summarizes the different scenarios you can use ViewCompositionStrategy in:

ViewCompositionStrategy Description and Interop Scenario
DisposeOnDetachedFromWindow The Composition will be disposed when the underlying ComposeView is detached from the window. Has since been superseded by DisposeOnDetachedFromWindowOrReleasedFromPool.

Interop scenario:

* ComposeView whether it’s the sole element in the View hierarchy, or in the context of a mixed View/Compose screen (not in Fragment).
DisposeOnDetachedFromWindowOrReleasedFromPool (Default) Similar to DisposeOnDetachedFromWindow, when the Composition is not in a pooling container, such as a RecyclerView. If it is in a pooling container, it will dispose when either the pooling container itself detaches from the window, or when the item is being discarded (i.e. when the pool is full).

Interop scenario:

* ComposeView whether it's the sole element in the View hierarchy, or in the context of a mixed View/Compose screen (not in Fragment).
* ComposeView as an item in a pooling container such as RecyclerView.
DisposeOnLifecycleDestroyed The Composition will be disposed when the provided Lifecycle is destroyed.

Interop scenario

* ComposeView in a Fragment's View.
DisposeOnViewTreeLifecycleDestroyed The Composition will be disposed when the Lifecycle owned by the LifecycleOwner returned by ViewTreeLifecycleOwner.get of the next window the View is attached to is destroyed.

Interop scenario:

* ComposeView in a Fragment's View.
* ComposeView in a View wherein the Lifecycle is not known yet.

ComposeView in Fragments

If you want to incorporate Compose UI content in a fragment or an existing View layout, use ComposeView and call its setContent() method. ComposeView is an Android View.

You can put the ComposeView in your XML layout just like any other View:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <TextView
      android:id="@+id/text"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content" />

  <androidx.compose.ui.platform.ComposeView
      android:id="@+id/compose_view"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />
</LinearLayout>

In the Kotlin source code, inflate the layout from the layout resource defined in XML. Then get the ComposeView using the XML ID, set a Composition strategy that works best for the host View, and call setContent() to use Compose.

class ExampleFragmentXml : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val view = inflater.inflate(R.layout.fragment_example, container, false)
        val composeView = view.findViewById<ComposeView>(R.id.compose_view)
        composeView.apply {
            // Dispose of the Composition when the view's LifecycleOwner
            // is destroyed
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                // In Compose world
                MaterialTheme {
                    Text("Hello Compose!")
                }
            }
        }
        return view
    }
}

Alternatively, you can also use view binding to obtain references to the ComposeView by referencing the generated binding class for your XML layout file:

class ExampleFragment : Fragment() {

    private var _binding: FragmentExampleBinding? = null

    // This property is only valid between onCreateView and onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentExampleBinding.inflate(inflater, container, false)
        val view = binding.root
        binding.composeView.apply {
            // Dispose of the Composition when the view's LifecycleOwner
            // is destroyed
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                // In Compose world
                MaterialTheme {
                    Text("Hello Compose!")
                }
            }
        }
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

Two slightly different text elements, one above the other

Figure 1. This shows the output of the code that adds Compose elements in a View UI hierarchy. The "Hello Android!" text is displayed by a TextView widget. The "Hello Compose!" text is displayed by a Compose text element.

You can also include a ComposeView directly in a fragment if your full screen is built with Compose, which lets you avoid using an XML layout file entirely.

class ExampleFragmentNoXml : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return ComposeView(requireContext()).apply {
            // Dispose of the Composition when the view's LifecycleOwner
            // is destroyed
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                MaterialTheme {
                    // In Compose world
                    Text("Hello Compose!")
                }
            }
        }
    }
}

Multiple ComposeView instances in the same layout

If there are multiple ComposeView elements in the same layout, each one must have a unique ID for savedInstanceState to work.

class ExampleFragmentMultipleComposeView : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View = LinearLayout(requireContext()).apply {
        addView(
            ComposeView(requireContext()).apply {
                setViewCompositionStrategy(
                    ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
                )
                id = R.id.compose_view_x
                // ...
            }
        )
        addView(TextView(requireContext()))
        addView(
            ComposeView(requireContext()).apply {
                setViewCompositionStrategy(
                    ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
                )
                id = R.id.compose_view_y
                // ...
            }
        )
    }
}

The ComposeView IDs are defined in the res/values/ids.xml file:

<resources>
  <item name="compose_view_x" type="id" />
  <item name="compose_view_y" type="id" />
</resources>

Preview composables in Layout Editor

You can also preview composables within the Layout Editor for your XML layout containing a ComposeView. Doing so lets you see how your composables look within a mixed Views and Compose layout.

Say you want to display the following composable in the Layout Editor. Note that composables annotated with @Preview are good candidates to preview in the Layout Editor.

@Preview
@Composable
fun GreetingPreview() {
    Greeting(name = "Android")
}

To display this composable, use the tools:composableName tools attribute and set its value to the fully qualified name of the composable to preview in the layout.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <androidx.compose.ui.platform.ComposeView
      android:id="@+id/my_compose_view"
      tools:composableName="com.example.compose.snippets.interop.InteroperabilityAPIsSnippetsKt.GreetingPreview"
      android:layout_height="match_parent"
      android:layout_width="match_parent"/>

</LinearLayout>

Composable displayed within layout editor

Next steps

Now that you know the interoperability APIs to use Compose in Views, learn how to use Views in Compose.