Logo
Published on

How to use Android View inside Jetpack Compose and vice versa?

Authors
  • Name
    Twitter

Pre-requisites:

  • Knowledge of Android App development.
  • Knowledge of Android view and Jetpack compose.

Use Android View inside Composable functions.

There are 4 use-case:

  1. Android View and layout without view binding
  2. Android View with view binding.
  3. Android View in Lazy list
  4. Fragment in Compose

1. Android View and layout without view binding

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](https://developers.google.com/android/reference/com/google/android/gms/ads/AdView). This approach also lets you reuse custom views you may have designed.

To include a view element or hierarchy, use the [AndroidView](https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary#AndroidView(kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1)) composable. AndroidView is passed a lambda that returns a [View](https://developer.android.com/reference/android/view/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(){
    // ADD VIEW TO COMPOSE
    AndroidView(
      modifier = Modifier,
      factory = {
            // Creates view
            MyView(context).apply {
                // Sets up listeners for View -> Compose communication
                setOnClickListener {
                   selectedItem = 1
                }
            }
            // Other Examples
            // Creates view
            StyledPlayerView(context).apply {
                player = exoPlayer
            }
            // or inflate layout inside composable
            View.inflate(it, R.layout.barcode_layout, null)
      },
      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
      }
    ) {
    }
}

Note: Prefer to construct a View in the **AndroidView** **factory** lambda instead of using **remember** to hold a View reference outside of **AndroidView**.

2. AndroidView with view binding

To embed an XML layout, use the [AndroidViewBinding](https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary#AndroidViewBinding(kotlin.Function3,%20androidx.compose.ui.Modifier,%20kotlin.Function1)) API, which is provided by the androidx.compose.ui:ui-viewbinding library. To do this, your project must enable view binding.

@Composable
fun AndroidViewBindingExample() {
AndroidViewBinding(ExampleLayoutBinding::inflate) {
exampleView.setBackgroundColor(Color.GRAY)
}
}

3. AndroidView in Lazy lists

If you are using an AndroidView in a Lazy list (LazyColumn, LazyRow, Pager, etc.), consider using the [AndroidView](https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary#AndroidView(kotlin.Function1,kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1,kotlin.Function1)) 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 the View is about to be reused.
  • onRelease (optional) - A callback invoked to signal that the View has exited the composition and will not be reused again.
@OptIn(ExperimentalComposeUiApi::class)
@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()
}
)
}
}
}

4. Fragments in Compose

Use the AndroidViewBinding composable to add a Fragment in Compose. AndroidViewBinding has fragment-specific handling such as removing the fragment when the composable leaves the composition.

Do so by inflating an XML containing a [FragmentContainerView](https://developer.android.com/reference/androidx/fragment/app/FragmentContainerView) as the holder for your Fragment.

For example, if you have the my_fragment_layout.xml defined, you could use code like this while replacing the android:name XML attribute with your Fragment's class name:

<androidx.fragment.app.FragmentContainerView  xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.compose.snippets.interop.MyFragment" />

Inflate this fragment in Compose as follows:

@Composable
fun FragmentInComposeExample() {
AndroidViewBinding(MyFragmentLayoutBinding::inflate) {
val myFragment = fragmentContainerView.getFragment<MyFragment>()
// ...
}
}

If you need to use multiple fragments in the same layout, ensure that you have defined a unique ID for each FragmentContainerView.

You can see the code here.

Note: It is initial Jetpack compose project. You can see

[_use-android-view-inside-jetpack-compose_](https://github.com/KaushalVasava/Xml_And_JetpackCompose_Interoperability/tree/use-android-view-inside-jetpack-compose) branch to learn how to use it.

Use Composable function inside Android View system.

Add required dependencies and compose compiler options.

Add below in your android block.

buildFeatures{
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.5"
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}

Add these dependencies in your dependencies block

def compose_version = "1.4.5"
// compose dependency
implementation 'androidx.activity:activity-compose:1.7.2'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.compose.material3:material3:1.2.0-alpha06'

androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"

1. Single Compose View in the layout

If you want to incorporate Compose UI content in a fragment or an existing View layout, use [ComposeView](https://developer.android.com/reference/kotlin/androidx/compose/ui/platform/ComposeView) and call its [setContent()](https://developer.android.com/reference/kotlin/androidx/compose/ui/platform/ComposeView#setContent(kotlin.Function0)) method. ComposeView is an Android [View](https://developer.android.com/reference/android/view/View).

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

Change your existing view layout with new compose 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:text="Android Text View"
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>

Inside Activity

To use Compose in Activity, set a ComposeView in the setContentView() method in OnCreate() .

// This is the code for the `MainActivity` class
class  MainActivity : AppCompatActivity() {

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

// Add a `ComposeView` to the activity's layout.
val composeView = ComposeView(this)
setContent {
// This is the Composable function that will be rendered in the `ComposeView`.
YourComposable()
}

setContentView(composeView)
}
}

Inside Fragment

In your fragment add this code, use [ComposeView](https://developer.android.com/reference/kotlin/androidx/compose/ui/platform/ComposeView) and call its [setContent()](https://developer.android.com/reference/kotlin/androidx/compose/ui/platform/ComposeView#setContent(kotlin.Function0)) method. ComposeView is an Android [View](https://developer.android.com/reference/android/view/View).

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

// ADD THIS CODE FOR YOUR COMPOSABLE
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
}
}

Output:

Android Text View

Hello Compose!

Remove textview, because now composeview do work for you.

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

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

Output: Hello Compose!

  • 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!")
}
}
}
}
}

Output: Hello Compose!

2. 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  ExampleFragment : Fragment() {
override  fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
return LinearLayout(requireContext()).apply {
// for vertical arrangement
orientation = LinearLayout.VERTICAL
// adds views
addView(
ComposeView(requireContext()).apply {
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
id = R.id.compose_view_1
setContent {
Text("Hello Compose 1")
}
}
)
addView(TextView(requireContext()).apply {
text = "Hello TextView"
textSize = 20f
})
addView(
ComposeView(requireContext()).apply {
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
id = R.id.compose_view_2
setContent {
Text("Hello Compose 2")
}
}
)
}
}
}

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

<resources>
<item name="compose_view_1" type="id" />
<item name="compose_view_2" type="id" />
</resources>

Layout file is just container view ie. LinearLayout. And views are added dynamically in this example.

OutPut:

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 = "Hello 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.ExmapleFragment.GreetingPreview"
      android:layout_height="match_parent"
      android:layout_width="match_parent"/>

</LinearLayout>

Preview

You can see the code here.*

Note: It is initial Android View project. You can see

[_use-jetpack-compose-inside-xml-layout_](https://github.com/KaushalVasava/Xml_And_JetpackCompose_Interoperability/tree/use-jetpack-compose-inside-xml-layout) branch to learn how to use it.

Thank you for reading.

Don’t forget to clap 👏 and follow me for more such useful articles about Android Development, Kotlin & KMP.

If you need any help related to Android, Kotlin and KMP. I’m always happy to help you. You can reach me on LinkedIn, Twitter, GitHub and Instagram.