Cómo usar Compose en Views

Puedes agregar una IU basada en Compose a una app existente que use un diseño basado en View.

Para crear una pantalla nueva basada íntegramente en Compose, haz que tu actividad llame al método setContent() y pasa las funciones de componibilidad que quieras.

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

Este código es similar al que se encuentra en una app exclusivamente de Compose.

ViewCompositionStrategy para ComposeView

ViewCompositionStrategy define cuándo se debe descartar la composición. El valor predeterminado, ViewCompositionStrategy.Default, descarta la composición cuando el ComposeView subyacente se desconecta de la ventana, a menos que sea parte de un contenedor de agrupación, como RecyclerView. En una app de actividad única de Compose, este comportamiento predeterminado es lo que deberías tener. Sin embargo, si agregas Compose de forma incremental a tu base de código, este comportamiento puede causar la pérdida de estado en algunas situaciones.

Para cambiar el ViewCompositionStrategy, llama al método setViewCompositionStrategy() y proporciona una estrategia diferente.

En la siguiente tabla, se resumen las diferentes situaciones en las que puedes usar ViewCompositionStrategy:

ViewCompositionStrategy Situación de interoperabilidad y descripción
DisposeOnDetachedFromWindow La composición se eliminará cuando el ComposeView subyacente se separe de la ventana. Luego, se reemplazó por DisposeOnDetachedFromWindowOrReleasedFromPool.

Situación de interoperabilidad:

* ComposeView si es el único elemento en la jerarquía de vistas o en el contexto de una pantalla mixta de View/Compose (no en Fragment).
DisposeOnDetachedFromWindowOrReleasedFromPool (Predeterminada) Similar a DisposeOnDetachedFromWindow, cuando la composición no está en un contenedor de agrupación, como RecyclerView. Si está en un contenedor de agrupación, se eliminará cuando el contenedor de reducción se desconecte de la ventana o cuando el elemento se descarte (es decir, cuando el grupo esté completo).

Situación de interoperabilidad:

* ComposeView ya sea el único elemento en la jerarquía de View o en el contexto de una pantalla mixta de View/Compose (no en Fragment).
* ComposeView como un elemento en un contenedor de grupos, como RecyclerView
DisposeOnLifecycleDestroyed La composición se eliminará cuando se destruya el Lifecycle proporcionado.

Situación de interoperabilidad

* ComposeView en la vista de un fragmento.
DisposeOnViewTreeLifecycleDestroyed La composición se eliminará cuando se destruya el Lifecycle que es propiedad del LifecycleOwner que muestra ViewTreeLifecycleOwner.get de la siguiente ventana a la que se adjunta la vista.

Situación de interoperabilidad:

* ComposeView en la vista de un fragmento
* ComposeView en una vista en la que aún no se conoce el ciclo de vida

ComposeView en Fragments

Si quieres incorporar contenido de la IU de Compose a un fragmento o un diseño de View existente, usa ComposeView y llama a su método setContent(). ComposeView es una View de Android.

Puedes colocar ComposeView en tu diseño XML como cualquier otro elemento 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>

En el código fuente de Kotlin, aumenta el diseño del recurso de diseño que se define en XML. Luego, obtén la ComposeView con el ID de XML, establece la estrategia de composición que mejor se adapte a la View del host y llama a setContent() para usar 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
    }
}

Como alternativa, también puedes usar la vinculación de vista para obtener referencias a ComposeView haciendo referencia a la clase de vinculación generada para tu archivo de diseño XML:

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

Dos elementos de texto ligeramente diferentes, uno sobre el otro

Figura 1: Muestra el resultado del código que agrega elementos de Compose a una jerarquía de la IU de View. El texto "Hello Android!" se muestra en un widget TextView. El texto "Hello Compose!" se muestra en un elemento de texto de Compose.

También puedes incluir una ComposeView directamente en un fragmento si toda tu pantalla está compilada con Compose, por lo que no necesitas usar un archivo de diseño XML.

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

Varias instancias de ComposeView en el mismo diseño

Si hay varios elementos ComposeView en el mismo diseño, cada uno debe tener un ID único para que savedInstanceState funcione.

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

Los ID de ComposeView se definen en el archivo res/values/ids.xml:

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

Cómo obtener una vista previa de elementos componibles en el editor de diseño

También puedes obtener una vista previa de elementos componibles dentro del editor de diseño para tu diseño XML que contenga un ComposeView. De esta manera, podrás ver cómo se ven los elementos componibles dentro de un diseño mixto de Views y Compose.

Supongamos que deseas mostrar el siguiente elemento componible en el editor de diseño. Ten en cuenta que los elementos componibles con anotaciones @Preview son buenos candidatos para obtener una vista previa en el editor de diseño.

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

Para mostrar este elemento componible, usa el atributo de herramientas de tools:composableName y establece su valor en el nombre completamente calificado del elemento componible para obtener una vista previa en el diseño.

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

El elemento componible se muestra dentro del editor de diseño

Próximos pasos

Ahora que conoces las APIs de interoperabilidad para usar Compose en Views, obtén información sobre cómo usar Views en Compose.