Cómo usar objetos View en Compose

Puedes incluir una jerarquía de vistas de Android en una IU de Compose. Este enfoque es particularmente útil si quieres usar elementos de la IU que aún no están disponibles en Compose, como AdView. Además, te permite volver a usar vistas personalizadas que ya hayas diseñado.

Para incluir un elemento o una jerarquía de vistas, usa el elemento componible AndroidView . AndroidView recibe una expresión lambda que muestra una View. AndroidView también proporciona una devolución de llamada update que se realiza cuando se aumenta la vista. La vista AndroidView se recompone cada vez que cambia una lectura de State dentro de la devolución de llamada. AndroidView, como muchos otros elementos componibles integrados, toma un parámetro Modifier que se puede utilizar, por ejemplo, para definir su posición en el elemento componible superior.

@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 con vinculación de vista

Para incorporar un diseño XML, usa la API de AndroidViewBinding, que proporciona la biblioteca androidx.compose.ui:ui-viewbinding. Para ello, tu proyecto debe habilitar la vinculación de vistas.

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

AndroidView en listas diferidas

Si usas un AndroidView en una lista diferida (LazyColumn, LazyRow, Pager, etcétera), considera usar la sobrecarga de AndroidView que se introdujo en la versión 1.4.0-rc01. Esta sobrecarga permite que Compose vuelva a usar la instancia subyacente de View cuando se vuelve a usar la composición que la contiene, como es el caso de las listas diferidas.

Esta sobrecarga de AndroidView agrega 2 parámetros adicionales:

  • onReset: Es una devolución de llamada que se invoca para indicar que se volverá a usar View. Este valor no debe ser nulo para habilitar la reutilización de objetos View.
  • onRelease (opcional): Es una devolución de llamada que se invoca para indicar que View salió de la composición y no se volverá a reutilizar.

@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()
                }
            )
        }
    }
}

Fragmentos en Compose

Usa el elemento componible AndroidViewBinding para agregar un Fragment en Compose. AndroidViewBinding tiene control específico de los fragmentos, de manera que puede quitar el fragmento cuando el elemento componible deja la composición.

Para ello, aumenta un XML que contenga una FragmentContainerView como contenedor de Fragment.

Por ejemplo, si tienes el my_fragment_layout.xml definido, puedes usar un código como este mientras reemplazas el atributo XML android:name por el nombre de clase de tu Fragment:

<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" />

Aumenta este fragmento en Compose de la siguiente manera:

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

Si necesitas usar varios fragmentos en el mismo diseño, asegúrate de haber definido un ID único para cada FragmentContainerView.

Cómo llamar al framework de Android desde Compose

Compose funciona dentro de las clases del framework de Android. Por ejemplo, se aloja en clases de Android View, como Activity o Fragment, y puede usar clases del framework de Android como Context, recursos del sistema, Service o BroadcastReceiver.

Para obtener más información sobre los recursos del sistema, consulta Recursos en Compose.

Configuraciones locales de composición

Las clases de CompositionLocal permiten pasar datos de manera implícita por funciones de componibilidad. En general, tienen un valor en un nodo determinado del árbol de IU. Sus subordinados de componibilidad pueden utilizar ese valor sin declarar la CompositionLocal como un parámetro en la función de componibilidad.

CompositionLocal se usa para propagar valores para los tipos de framework de Android en Compose como Context, Configuration o la View en la que está alojado el código de Compose con los elementos LocalContext, LocalConfiguration o LocalView. Ten en cuenta que las clases CompositionLocal tienen el prefijo Local para darles mayor visibilidad con la función de autocompletar en el IDE.

Para acceder al valor actual de una CompositionLocal, usa su propiedad current. Por ejemplo, el siguiente código proporciona LocalContext.current en el método Toast.makeToast para mostrar un mensaje de aviso.

@Composable
fun ToastGreetingButton(greeting: String) {
    val context = LocalContext.current
    Button(onClick = {
        Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show()
    }) {
        Text("Greet")
    }
}

Para obtener un ejemplo más completo, consulta la sección Caso de éxito: BroadcastReceivers al final de este documento.

Otras interacciones

Si no existe una utilidad definida para la interacción que necesitas, te recomendamos que sigas el lineamiento general de Compose: que los datos circulen hacia abajo y los eventos hacia arriba (que se aborda en más detalle en Acerca de Compose). Por ejemplo, este elemento que admite composición ejecuta otra actividad:

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)
    }
}

Caso de éxito: BroadcastReceivers

Si deseas ver un ejemplo de funciones más realista, quizás te convenga migrar o implementar en Compose. Para mostrar CompositionLocal y los efectos secundarios, es necesario registrar un BroadcastReceiver, por ejemplo, desde una función de componibilidad.

La solución emplea LocalContext para usar el contexto actual, además de los efectos secundarios rememberUpdatedState y DisposableEffect.

@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 */
}

Próximos pasos

Ahora que conoces las APIs de interoperabilidad cuando usas Compose en Views y viceversa, explora la página Otras consideraciones para obtener más información.