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.
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 { mutableStateOf(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() } }
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.
@Composable fun AndroidViewBindingExample() { AndroidViewBinding(ExampleLayoutBinding::inflate) { exampleView.setBackgroundColor(Color.GRAY) } }
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 theView
is about to be reused. This must be non-null to enable View reuse.onRelease
(optional) - A callback invoked to signal that theView
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() } ) } } }
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
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
.
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") } }
For a more complete example, take a look at the Case Study: BroadcastReceivers section at the end of this document.
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) } }
Case study: Broadcast receivers
For a more realistic example of features you might want to migrate or implement
in Compose, and to showcase CompositionLocal
and side
effects, let's say a
BroadcastReceiver
needs to be registered from
a composable function.
The solution makes 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 */ }
Next steps
Now that you know the interoperability APIs when using Compose in Views and vice versa, explore the Other considerations page to learn more.
Recommended for you
- Note: link text is displayed when JavaScript is off
- Other considerations
- Side-effects in Compose
- Locally scoped data with CompositionLocal