Cómo agregar Compose a una app basada en objetos View

1. Antes de comenzar

Desde el comienzo, Jetpack Compose se diseñó con interoperabilidad con View, es decir que Compose y el sistema de vistas pueden compartir recursos y funcionar en conjunto para mostrar la IU. Esta funcionalidad te permite agregar Compose a tu app existente basada en objetos View. Es decir, los elementos Compose y View pueden coexistir en tu base de código hasta que toda tu app esté hecha por completo con Compose.

En este codelab, pasarás a Compose el elemento de lista hecho con vistas de la app de Juice Tracker. Si lo deseas, puedes convertir el resto de las vistas de Juice Tracker por tu cuenta.

Si tienes una app con una IU basada en objetos View, es posible que no quieras volver a escribir toda su IU de una sola vez. Este codelab te ayudará a convertir un elemento View independiente de una IU que se basa en esos objetos a un elemento Compose.

Requisitos previos

  • Conocimientos básicos de la IU basada en objetos View
  • Conocimientos para compilar una app con una IU basada en objetos View
  • Experiencia con la sintaxis de Kotlin, incluidas las funciones de lambdas
  • Conocimientos para compilar una app en Jetpack Compose

Qué aprenderás

  • Cómo agregar Compose a una pantalla existente que se compiló con vistas de Android
  • Cómo obtener una vista previa de un elemento componible agregado a tu app basada en objetos View

Qué compilarás

  • Convertirás un elemento de lista basado en objetos View a Compose en la app de Juice Tracker.

2. Descripción general de la app de inicio

En este codelab, se usa el código de solución de la app de Juice Tracker que se creó en Cómo compilar una app para Android con vistas como código de partida. La app de partida ya guarda datos con la biblioteca de persistencias Room. El usuario puede agregar información de jugos a la base de datos de la app, como el nombre del jugo, una descripción, un color y una calificación.

Pantalla de teléfono en la que se muestran jugos con detalles y calificaciones

En este codelab, convertirás a Compose el elemento de lista basado en objetos View.

Elemento de la lista con detalles del jugo

Descarga el código de partida para este codelab

Para comenzar, descarga el código de partida:

Descargar ZIP

Como alternativa, puedes clonar el repositorio de GitHub para el código:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views

Puedes explorar el código en el repositorio de GitHub de JuiceTracker.

3. Agrega la biblioteca de Jetpack Compose

Los objetos Recollect, Compose y View pueden coexistir en una pantalla determinada. Puedes tener algunos elementos de la IU en Compose y otros en el sistema de View. Por ejemplo, puedes tener solo la lista en Compose, mientras que el resto de la pantalla está en el sistema de View.

Completa los siguientes pasos para agregar la biblioteca de Compose a la app de Juice Tracker.

  1. Abre Juice Tracker en Android Studio.
  2. Abre build.gradle.kts al nivel de la app.
  3. Dentro del bloque buildFeatures, agrega una marca compose = true.
buildFeatures {
    //...
    // Enable Jetpack Compose for this module
    compose = true
}

Esta marca permite que Android Studio funcione con Compose. No creaste este código en los codelabs anteriores porque Android Studio lo genera automáticamente cuando creas un nuevo proyecto de plantilla con Compose.

  1. Debajo de buildFeatures, agrega el bloque composeOptions.
  2. Dentro del bloque, establece kotlinCompilerExtensionVersion en "1.5.1" para establecer la versión del compilador de Kotlin.
composeOptions {
    kotlinCompilerExtensionVersion = "1.5.1"
}
  1. En la sección dependencies, agrega las dependencias de Compose. Necesitas las siguientes dependencias para agregar Compose a una app basada en View. Estas dependencias ayudan a integrar Compose con Activity, agregar la biblioteca de componentes de diseño de Compose, admitir temas de Compose Jetpack y proporcionar herramientas para brindar una mejor compatibilidad con IDE.
dependencies {
    implementation(platform("androidx.compose:compose-bom:2023.06.01"))
    // other dependencies
    // Compose
    implementation("androidx.activity:activity-compose:1.7.2")
    implementation("androidx.compose.material3:material3")
    implementation("com.google.accompanist:accompanist-themeadapter-material3:0.28.0")

    debugImplementation("androidx.compose.ui:ui-tooling")
}

Agrega un objeto ComposeView

Un objeto ComposeView es una vista de Android que puede alojar contenido de la IU de Jetpack Compose. Usa setContent para proporcionar la función de componibilidad de contenido de la vista.

  1. Abre el archivo layout/list_item.xml y obtén una vista previa en la pestaña Split.

Al final de este codelab, reemplazarás el objeto View por un elemento componible.

f85c6002df3265e0.png

  1. En JuiceListAdapter.kt, para resolver el error, quita ListItemBinding de todas partes. En la clase JuiceListViewHolder, reemplaza binding.root por composeView.
import androidx.compose.ui.platform.ComposeView

class JuiceListViewHolder(
    private val onEdit: (Juice) -> Unit,
    private val onDelete: (Juice) -> Unit
): RecyclerView.ViewHolder(composeView)

Para resolver el error, debes quitar ListItemBinding de todas partes.

  1. En la carpeta onCreateViewHolder(), actualiza la función return() para que coincida con el siguiente código:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): JuiceListViewHolder {
   return JuiceListViewHolder(
       ComposeView(parent.context),
       onEdit,
       onDelete
   )
}
  1. En la clase JuiceListViewHolder, borra todas las variables private y quita todo el código de la función bind(). La clase JuiceListViewHolder ahora luce como el siguiente código:
class JuiceListViewHolder(
    private val onEdit: (Juice) -> Unit,
    private val onDelete: (Juice) -> Unit
) : RecyclerView.ViewHolder(composeView) {

   fun bind(juice: Juice) {

   }
}
  1. En este momento, puedes borrar las importaciones com.example.juicetracker.databinding.ListItemBinding y android.view.LayoutInflater.
// Delete
import com.example.juicetracker.databinding.ListItemBinding
import android.view.LayoutInflater
  1. Borra el archivo layout/list_item.xml.
  2. Selecciona OK en el cuadro de diálogo Delete.

86dd7cba7e181e54.png

4. Agrega una función de componibilidad

A continuación, crearás un elemento componible que emite el elemento de la lista. El elemento componible toma Juice y dos funciones de devolución de llamada para editar y borrar el elemento de la lista.

  1. En JuiceListAdapter.kt, después de la definición de la clase JuiceListAdapter, crea una función de componibilidad llamada ListItem().
  2. Haz que la función ListItem() acepte el objeto Juice y una devolución de llamada lambda para borrar.
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

@Composable
fun ListItem(
    input: Juice,
    onDelete: (Juice) -> Unit,
    modifier: Modifier = Modifier
) {
}

Echa un vistazo a la vista previa del elemento de la lista que deseas crear. Observa que tiene un ícono de jugo, detalles del jugo y un ícono de botón para borrar. En breve, implementarás estos componentes.

cf3b235dcb93e998.png

Crea un elemento Juice componible

  1. En JuiceListAdapter.kt, después del elemento componible ListItem(), crea otra función de componibilidad llamada JuiceIcon() que tome un color y un Modifier.
@Composable
fun JuiceIcon(color: String, modifier: Modifier = Modifier) {

}
  1. Dentro de la función JuiceIcon(), agrega variables de color y la descripción del contenido como se muestra en el siguiente código:
@Composable
fun JuiceIcon(color: String, modifier: Modifier = Modifier) {
   val colorLabelMap = JuiceColor.values().associateBy { stringResource(it.label) }
   val selectedColor = colorLabelMap[color]?.let { Color(it.color) }
   val juiceIconContentDescription = stringResource(R.string.juice_color, color)

}

Con las variables colorLabelMap y selectedColor, recuperarás el recurso de color asociado con la selección del usuario.

  1. Agrega un diseño Box para mostrar un ícono ic_juice_color y un ícono ic_juice_clear, uno encima del otro. El ícono ic_juice_color tiene un tono y está alineado con el centro.
import androidx.compose.foundation.layout.Box

Box(
   modifier.semantics {
       contentDescription = juiceIconContentDescription
   }
) {
   Icon(
       painter = painterResource(R.drawable.ic_juice_color),
       contentDescription = null,
       tint = selectedColor ?: Color.Red,
       modifier = Modifier.align(Alignment.Center)
   )
   Icon(painter = painterResource(R.drawable.ic_juice_clear), contentDescription = null)
}

Como ya tienes conocimientos sobre implementación de elementos componibles, no se proporcionan detalles de cómo se hace.

  1. Agrega una función para obtener una vista previa de JuiceIcon(). Pasa el color como Yellow.
import androidx.compose.ui.tooling.preview.Preview

@Preview
@Composable
fun PreviewJuiceIcon() {
    JuiceIcon("Yellow")
}

Vista previa del ícono de jugo amarillo

Crea detalles de jugos componibles

En JuiceListAdapter.kt, debes agregar otra función de componibilidad para mostrar los detalles de los jugos. También necesitas un diseño de columna para mostrar dos elementos componibles Text de nombre y descripción, además de un indicador de calificación. Para ello, completa los siguientes pasos:

  1. Agrega una función de componibilidad llamada JuiceDetails() que tome un objeto Juice y un Modifier, un texto componible para el nombre del jugo y un elemento componible para la descripción del jugo, como se muestra en el siguiente código:
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.ui.text.font.FontWeight

@Composable
fun JuiceDetails(juice: Juice, modifier: Modifier = Modifier) {
   Column(modifier, verticalArrangement = Arrangement.Top) {
       Text(
           text = juice.name,
           style = MaterialTheme.typography.h5.copy(fontWeight = FontWeight.Bold),
       )
       Text(juice.description)
       RatingDisplay(rating = juice.rating, modifier = Modifier.padding(top = 8.dp))
   }
}
  1. Para resolver el error de referencia sin resolver, crea una función de componibilidad llamada RatingDisplay().

4018a1be2b3e7399.png

En el sistema de vistas, tienes un elemento RatingBar para mostrar la siguiente barra de calificación. Compose no tiene una barra de calificación componible, por lo que debes implementar este elemento desde cero.

  1. Define la función RatingDisplay() para mostrar las estrellas según la calificación. Esta función de componibilidad muestra la cantidad de estrellas según la calificación.

Barra de calificaciones con cuatro estrellas

import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource

@Composable
fun RatingDisplay(rating: Int, modifier: Modifier = Modifier) {
   val displayDescription = pluralStringResource(R.plurals.number_of_stars, count = rating)
   Row(
       // Content description is added here to support accessibility
       modifier.semantics {
           contentDescription = displayDescription
       }
   ) {
       repeat(rating) {
           // Star [contentDescription] is null as the image is for illustrative purpose
           Image(
               modifier = Modifier.size(32.dp),
               painter = painterResource(R.drawable.star),
               contentDescription = null
           )
       }
   }
}

Para crear el elemento de diseño de estrella en Compose, debes crear el recurso vectorial de estrella.

  1. En el panel Project, haz clic con el botón derecho en drawable > New > Vector Asset.

e3b2bd6a495bc9.png

  1. En el diálogo Asset Studio, busca el ícono de estrella. Selecciona el ícono de estrella con relleno.

Diálogo Select icon con el ícono de estrella seleccionado

  1. Cambia el valor de color de la estrella a 625B71.

Diálogo Asset Studio con el color y Configure Vector Asset

  1. Haz clic en Next > Finish.
  2. Observa que aparece un elemento de diseño en la carpeta res/drawable.

Panel Project en Android Studio con la carpeta res drawable

  1. Agrega un elemento componible de vista previa para obtener una vista previa del elemento JuiceDetails componible.
@Preview
@Composable
fun PreviewJuiceDetails() {
    JuiceDetails(Juice(1, "Sweet Beet", "Apple, carrot, beet, and lemon", "Red", 4))
}

con el nombre del jugo, su descripción y una barra de calificación por estrellas

Crea un elemento componible de botón Borrar

  1. En JuiceListAdapter.kt, agrega otra función de componibilidad llamada DeleteButton() que tome una función de devolución de llamada lambda y un modificador.
  2. Configura la lambda para el argumento onClick y pasa el Icon() como se muestra en el siguiente código:
import androidx.compose.ui.res.painterResource
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton

@Composable
fun DeleteButton(onDelete: () -> Unit, modifier: Modifier = Modifier) {
    IconButton(
        onClick = { onDelete() },
        modifier = modifier
    ) {
        Icon(
            painter = painterResource(R.drawable.ic_delete),
            contentDescription = stringResource(R.string.delete)
        )
    }
}
  1. Agrega una función de vista previa para obtener una vista previa del botón Borrar.
@Preview
@Composable
fun PreviewDeleteIcon() {
    DeleteButton({})
}

Vista previa del ícono de eliminación en Android Studio

5. Implementa la función ListItem

Ahora que tienes todos los elementos componibles necesarios para mostrar el elemento de la lista, puedes organizarlos en un diseño. Observa la función ListItem() que definiste en el paso anterior.

@Composable
fun ListItem(
   input: Juice,
   onEdit: (Juice) -> Unit,
   onDelete: (Juice) -> Unit,
   modifier: Modifier = Modifier
) {
}

En JuiceListAdapter.kt, completa los siguientes pasos para implementar la función ListItem().

  1. Agrega un diseño Row dentro de la lambda Mdc3Theme {}.
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import com.google.accompanist.themeadapter.material3.Mdc3Theme

Mdc3Theme {
   Row(
       modifier = modifier,
       horizontalArrangement = Arrangement.SpaceBetween
   ) {

   }
}
  1. Dentro de la lambda Row, llama a los tres elementos JuiceIcon, JuiceDetails y DeleteButton componibles que creaste como elementos secundarios.
JuiceIcon(input.color)
JuiceDetails(input, Modifier.weight(1f))
DeleteButton({})

Pasar el Modifier.weight(1f) al elemento JuiceDetails() componible garantiza que los detalles del jugo ocupen el espacio horizontal restante después de medir los elementos secundarios no ponderados

  1. Pasa el modificador y la lambda onDelete(input) con la alineación superior como parámetros al elemento componible DeleteButton.
DeleteButton(
   onDelete = {
       onDelete(input)
   },
   modifier = Modifier.align(Alignment.Top)
)
  1. Escribe una función de vista previa para obtener una vista previa del elemento ListItem componible.
@Preview
@Composable
fun PreviewListItem() {
   ListItem(Juice(1, "Sweet Beet", "Apple, carrot, beet, and lemon", "Red", 4), {})
}

Vista previa del elemento de lista en Android Studio con detalles de jugo de remolacha

  1. Vincula el elemento ListItem componible con el contenedor de vistas. Llama a onEdit(input) dentro de la función lambda clickable() para abrir el diálogo de edición cuando se hace clic en el elemento de la lista.

En la clase JuiceListViewHolder, dentro de la función bind(), debes alojar el elemento componible. Usas ComposeView, que es una vista de Android que puede alojar contenido de la IU de Compose con el método setContent.

fun bind(input: Juice) {
    composeView.setContent {
        ListItem(
            input,
            onDelete,
            modifier = Modifier
                .fillMaxWidth()
                .clickable {
                    onEdit(input)
                }
                .padding(vertical = 8.dp, horizontal = 16.dp),
       )
   }
}
  1. Ejecuta la app. Agrega tu jugo favorito. Observa el elemento brillante de la lista de Compose.

Pantalla de teléfono con detalles del jugo completados en el diálogo de entrada. Pantalla de teléfono con un jugo en la lista

¡Felicitaciones! Acabas de crear tu primera app de interoperabilidad con Compose que usa elementos de Compose en una app basada en objetos View.

6. Obtén el código de la solución

Para descargar el código del codelab terminado, puedes usar estos comandos de git:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views-with-compose

También puedes descargar el repositorio como un archivo ZIP, descomprimirlo y abrirlo en Android Studio.

Descargar ZIP

Si deseas ver el código de la solución, puedes hacerlo en GitHub.

7. Más información

Documentación para desarrolladores de Android

Codelab [Intermedio]