Skip to main content
Donovan LaDuke - Developer

Using Compose Views in XML

Most developers in their day-to-day work don't have the luxury of working in a 100% Jetpack Compose codebase and that means having to interop with the XML view system. Thankfully there is support for these use cases. This week will cover how to use Composables inside XML layouts and the next article will cover how to use Views inside your Composables. Here's a rundown on what that looks like in a project.

Composables in XML #

An easy way to start exploring migrating to Jetpack Compose is to replace a low priority existing screen or build a new screen using Jetpack Compose. If the app is using the common "Single Activity/Multiple Fragments" approach, the new Composable screen can be initialized from the Fragment's onCreateView callback. First, create a ComposeView to host your Composable. This could be done with a generic XML view with a single ComposeView at the root, or by creating the creating the view programmatically to avoid XML altogether. Then set the ViewCompositionStrategy to be DisposeOnViewTreeLifecycleDestroyed to ensure the composition is correctly destroyed when the view's lifecycle owner is destroyed. See the example below for how this looks in code.

@Composable
fun MyNewScreen() { ... }

class MyNewFragment: Fragment() {
  override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
  ): View {
    val composeView = ComposeView(requireContext())

    composeView.setViewCompositionStrategy(
      ViewCompositionStrategy
        .DisposeOnViewTreeLifecycleDestroyed
    )

    composeView.setContent {
      MyNewScreen()
    }

    return composeView
  }
}

Alternatively, migration can be done by replacing just part of a screen, a single component for example. This is done similarly to the "whole screen" example, but instead of creating a new view, a ComposeView is added to the XML where the Composable will live. After that, the code looks very similar with setting the ViewCompositionStrategy and the content as before. The example below shows this done with findViewById but it can also be done using view binding.

// XML
<!-- OTHER VIEWS -->

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

<!-- OTHER VIEWS -->
// Fragment
@Composable
fun MyNewComponent() { ... }

class MyFragment: Fragment() {
  override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
  ): View {
    val view = inflater.inflate(
      R.layout.my_fragment, 
      container, 
      false
    )
    val composeView = view.findViewById<ComposeView>(
      R.id.my_new_component
    )

    // Same as before
    composeView.setViewCompositionStrategy(
      ViewCompositionStrategy
        .DisposeOnViewTreeLifecycleDestroyed
    )

    composeView.setContent {
      MyNewScreen()
    }

    return view
  }
}

Conclusion #

The migration story from XML to Compose is just that simple. As more of the app's views and screens begin to be migrated over to Compose, be sure to investigate that you are using the right ViewCompositionStrategy and be sure to specify specific ids for compose views as you move more views on a screen to Compose. For more information check out the documentation on the Android Developers site. The next post will cover the other half of the interop story, using XML views in a Composable view. Until next time, thanks!

Did you find this content helpful?

Please share this post and be sure to subscribe to the RSS feed to be notified of all future articles!

Want to go above and beyond? Help me out by sending me $1 on Ko-fi. It goes a long way in helping run this site and keeping it advertisement free. Thank you in advance!

Buy me a Coffee on Ko-fi