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, elimina la composición cuando el ComposeView subyacente se separa de la ventana, a menos que forme parte de un contenedor de grupo, como un RecyclerView. En una app de solo Compose de una sola actividad, este comportamiento predeterminado es lo que deseas. Sin embargo, si agregas Compose de forma incremental a tu base de código, este comportamiento puede provocar 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 Descripción y escenario de interoperabilidad
DisposeOnDetachedFromWindow La composición se eliminará cuando el ComposeView subyacente se desconecte de la ventana. Desde entonces, se reemplazó por DisposeOnDetachedFromWindowOrReleasedFromPool.

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).
DisposeOnDetachedFromWindowOrReleasedFromPool (Predeterminado) Similar a DisposeOnDetachedFromWindow, cuando la composición no está en un contenedor de agrupación, como un RecyclerView. Si está en un contenedor de grupo, se eliminará cuando el contenedor de grupo se desconecte de la ventana o cuando se descarte el elemento (es decir, cuando el grupo esté lleno).

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 agrupación, 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 pertenece al 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 vistas 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 los elementos componibles en el editor de diseño

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

Supongamos que quieres 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>

Elemento componible que se muestra en el 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.