1. Introducción
Compose y el sistema de View pueden funcionar en conjunto.
En este codelab, migrarás partes de la pantalla de detalles de plantas de Sunflower a Compose. Creamos una copia del proyecto para que pruebes migrar una app realista a Compose.
Al final del codelab, podrás continuar con la migración y convertir el resto de las pantallas de Sunflower si lo deseas.
Para obtener más asistencia mientras realizas este codelab, consulta el siguiente código:
Qué aprenderás
En este codelab, aprenderás lo siguiente:
- Cuáles son las diferentes rutas de migración que puedes seguir
- Cómo migrar una app de forma incremental a Compose
- Cómo agregar Compose a una pantalla existente compilada con vistas de Android
- Cómo usar una vista de Android desde Compose
- Cómo puedes usar tu tema del sistema de View en Compose
- Cómo probar una pantalla con el sistema de View y el código de Compose
Requisitos previos
- Tener experiencia con la sintaxis de Kotlin, incluidas las funciones de lambdas
- Conocer los conceptos básicos de Compose
Lo que necesitarás
2. Cómo planificar la migración
La migración a Compose depende de ti y de tu equipo. Hay muchas maneras de integrar Jetpack Compose en una app para Android existente. Las siguientes son dos estrategias de migración comunes:
- Desarrollar una pantalla nueva por completo con Compose
- Tomar una pantalla existente y migrar sus componentes de forma gradual
Compose en pantallas nuevas
Un enfoque común cuando haces la refactorización de tu app hacia una nueva tecnología es adoptarla en nuevas funciones que compilas para ella. En este caso, se aplican las pantallas nuevas. Si necesitas compilar una nueva pantalla de la IU para tu app, considera usar Compose mientras el resto de la app permanezca en el sistema de View.
En este caso, implementarás interoperabilidad de Compose en los bordes de esas funciones migradas.
Compose y View en conjunto
Dada una pantalla, puedes hacer que algunas partes migren a Compose y otras permanezcan en el sistema de View. Por ejemplo, puedes migrar una RecyclerView mientras dejas el resto de la pantalla en el sistema de View.
O, por el contrario, usa Compose como diseño externo y utiliza algunas vistas existentes que podrían no estar disponibles en Compose, como MapView o AdView.
Cómo finalizar la migración
Migra pantallas o fragmentos enteros a Compose uno por uno. Es una opción más simple, pero poco sofisticada.
¿Y este codelab?
En este codelab, realizarás una migración incremental a Compose de la pantalla de detalles de plantas de Sunflower y harás que Compose y View trabajen en conjunto. Luego, si lo deseas, sabrás lo suficiente para continuar la migración.
3. Cómo prepararte
Obtén el código
Obtén el código del codelab de GitHub:
$ git clone https://github.com/googlecodelabs/android-compose-codelabs
También tienes la opción de descargar el repositorio como archivo ZIP:
Abre Android Studio
Este codelab requiere Android Studio Bumblebee.
Cómo ejecutar la app de ejemplo
El código que acabas de descargar contiene código para todos los codelabs de Compose disponibles. Para completar este codelab, abre el proyecto MigrationCodelab en Android Studio.
En este codelab, migrarás la pantalla de detalles de plantas de Sunflower a Compose. Para abrir esta pantalla, presiona una de las plantas disponibles en la pantalla que muestra la lista de plantas.

Configuración del proyecto
El proyecto se compila en varias ramas de git:
maines la rama que revisaste o descargaste. Este es el punto de partida del codelab.endcontiene la solución para este codelab.
Te recomendamos que comiences con el código de la rama main y sigas el codelab paso a paso a tu propio ritmo.
Durante el codelab, recibirás fragmentos de código que deberás agregar al proyecto. En algunos lugares, también deberás quitar el código que se mencionará explícitamente en los comentarios de los fragmentos de código.
Para obtener la rama end con Git, usa el siguiente comando:
$ git clone -b end https://github.com/googlecodelabs/android-compose-codelabs
También puedes descargar el código de la solución aquí:
Preguntas frecuentes
4. Compose en Sunflower
Compose ya se agregó al código que descargaste de la rama main. Sin embargo, veamos qué se requiere para que funcione.
Si abres el archivo app/build.gradle (o build.gradle (Module: compose-migration.app)), verás la manera en que importa las dependencias de Compose y permite que Android Studio funcione con Compose con la marca buildFeatures { compose true }.
app/build.gradle
android {
...
kotlinOptions {
jvmTarget = '1.8'
useIR = true
}
buildFeatures {
...
compose true
}
composeOptions {
kotlinCompilerExtensionVersion rootProject.composeVersion
}
}
dependencies {
...
// Compose
implementation "androidx.compose.runtime:runtime:$rootProject.composeVersion"
implementation "androidx.compose.ui:ui:$rootProject.composeVersion"
implementation "androidx.compose.foundation:foundation:$rootProject.composeVersion"
implementation "androidx.compose.foundation:foundation-layout:$rootProject.composeVersion"
implementation "androidx.compose.material:material:$rootProject.composeVersion"
implementation "androidx.compose.runtime:runtime-livedata:$rootProject.composeVersion"
implementation "androidx.compose.ui:ui-tooling:$rootProject.composeVersion"
implementation "com.google.android.material:compose-theme-adapter:$rootProject.composeVersion"
...
}
En el archivo raíz build.gradle, se define la versión de esas dependencias.
5. ¡Hola, Compose!
En la pantalla de detalles de plantas, migraremos la descripción de la planta a Compose y dejaremos intacta la estructura general de la pantalla. A continuación, seguirá la estrategia de migración Compose y View en conjunto que se menciona en la sección Cómo planificar la migración.
Compose necesita una Actividad o un Fragmento de host para renderizar la IU. En Sunflower, como todas las pantallas usan fragmentos, usarás ComposeView: una Vista de Android que puede alojar contenido de la IU de Compose mediante su método setContent.
Cómo quitar el código XML
Comencemos con la migración. Abre fragment_plant_detail.xml y haz lo siguiente:
- Cambia a la Vista de código.
- Quita el código
ConstraintLayouty los objetosTextViewanidados dentro de laNestedScrollView(el codelab comparará el código XML y hará referencia a él cuando migres elementos individuales, por lo que será útil que tengas el código comentado). - Agrega un
ComposeViewque alojará el código de Compose concompose_viewcomo ID de vista.
fragment_plant_detail.xml
<androidx.core.widget.NestedScrollView
android:id="@+id/plant_detail_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="@dimen/fab_bottom_padding"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
// Step 2) Comment out ConstraintLayout and its children
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/margin_normal">
<TextView
android:id="@+id/plant_detail_name"
...
</androidx.constraintlayout.widget.ConstraintLayout>
// End Step 2) Comment out until here
// Step 3) Add a ComposeView to host Compose code
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.core.widget.NestedScrollView>
Cómo agregar código de Compose
Ya tienes todo listo para comenzar a migrar la pantalla de detalles de plantas a Compose.
A lo largo del codelab, agregarás código de Compose al archivo PlantDetailDescription.kt en la carpeta plantdetail. Ábrelo y observa que ya tenemos un texto de marcador de posición "Hello Compose!" disponible en el proyecto.
plantdetail/PlantDetailDescription.kt
@Composable
fun PlantDetailDescription() {
Text("Hello Compose")
}
A fin de mostrar esto en la pantalla, llama a este elemento que admite composición desde el ComposeView que agregamos en el paso anterior. Abre plantdetail/PlantDetailFragment.kt.
Como la pantalla usa la vinculación de datos, puedes acceder directamente al composeView y llamar a setContent para mostrar el código de Compose en la pantalla. Llama al elemento PlantDetailDescription que admite composición dentro de MaterialTheme, ya que Sunflower usa Material Design.
plantdetail/PlantDetailFragment.kt
class PlantDetailFragment : Fragment() {
...
override fun onCreateView(...): View? {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
...
composeView.setContent {
// You're in Compose world!
MaterialTheme {
PlantDetailDescription()
}
}
}
...
}
}
Si ejecutas la app, verás que se muestra "Hello Compose!" en la pantalla.

6. Cómo crear un elemento que admite composición a partir de XML
Comencemos por migrar el nombre de la planta. Más precisamente, el TextView con el ID @+id/plant_detail_name que quitaste en fragment_plant_detail.xml. Este es el código XML:
<TextView
android:id="@+id/plant_detail_name"
...
android:layout_marginStart="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
android:text="@{viewModel.plant.name}"
android:textAppearance="?attr/textAppearanceHeadline5"
... />
Observa que tiene un estilo textAppearanceHeadline5, con un margen horizontal de 8.dp y centrado de forma horizontal en la pantalla. Sin embargo, el título que se mostrará se observa desde un LiveData expuesto por PlantDetailViewModel que proviene de la capa del repositorio.
Como observar un LiveData se revisará más adelante, asumamos que el nombre está disponible y se lo pasa como parámetro a un elemento nuevo PlantName que admite composición y que creamos en el archivo PlantDetailDescription.kt. Luego, se llamará a este elemento desde el elemento PlantDetailDescription que admite composición.
PlantDetailDescription.kt
@Composable
private fun PlantName(name: String) {
Text(
text = name,
style = MaterialTheme.typography.h5,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.margin_small))
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
@Preview
@Composable
private fun PlantNamePreview() {
MaterialTheme {
PlantName("Apple")
}
}
Con vista previa:

Dónde:
- El estilo de
TextesMaterialTheme.typography.h5, que se asigna atextAppearanceHeadline5desde el código XML. - Los modificadores decoran el texto a fin de ajustarlo de modo que se vea como la versión XML.
- El modificador
fillMaxWidthcorresponde alandroid:layout_width="match_parent"en el código XML: - El
paddinghorizontal demargin_smalles un valor del sistema de View que usa la función auxiliardimensionResource. wrapContentWidthse utiliza para alinear elTexthorizontalmente.
7. ViewModels y LiveData
Ahora, conectemos el título a la pantalla. A tal fin, deberás cargar los datos mediante el PlantDetailViewModel. Para ello, Compose incluye integraciones para ViewModel y LiveData.
ViewModels
Como se usa una instancia de PlantDetailViewModel en el Fragmento, podríamos pasarla como un parámetro a PlantDetailDescription y eso sería suficiente.
Abre el archivo PlantDetailDescription.kt y agrega el parámetro PlantDetailViewModel a PlantDetailDescription:
PlantDetailDescription.kt
@Composable
fun PlantDetailDescription(plantDetailViewModel: PlantDetailViewModel) {
...
}
Ahora, pasa la instancia del ViewModel cuando llames a este elemento que admite composición desde el fragmento:
PlantDetailFragment.kt
class PlantDetailFragment : Fragment() {
...
override fun onCreateView(...): View? {
...
composeView.setContent {
MaterialTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
LiveData
Con esto, ya tienes acceso al campo LiveData<Plant> de PlantDetailViewModel a fin de obtener el nombre de la planta.
Para observar LiveData desde un elemento que admite composición, usa la función LiveData.observeAsState().
Dado que los valores emitidos por LiveData pueden ser nulos, deberás unir su uso en una verificación de nulidad. Por ese motivo y para fines de reutilización, es un buen patrón dividir el consumo y la escucha de LiveData en diferentes elementos que admiten composición. Por lo tanto, crea un nuevo elemento llamado PlantDetailContent que muestre la información de Plant.
Como se indicó anteriormente, así se ve el archivo PlantDetailDescription.kt después de agregar la observación de LiveData.
PlantDetailDescription.kt
@Composable
fun PlantDetailDescription(plantDetailViewModel: PlantDetailViewModel) {
// Observes values coming from the VM's LiveData<Plant> field
val plant by plantDetailViewModel.plant.observeAsState()
// If plant is not null, display the content
plant?.let {
PlantDetailContent(it)
}
}
@Composable
fun PlantDetailContent(plant: Plant) {
PlantName(plant.name)
}
@Preview
@Composable
private fun PlantDetailContentPreview() {
val plant = Plant("id", "Apple", "description", 3, 30, "")
MaterialTheme {
PlantDetailContent(plant)
}
}
Con la misma vista previa que PlantNamePreview, ya que PlantDetailContent solo llama a PlantName en este momento:

Conectaste el ViewModel por completo para mostrar el nombre de una planta en Compose. En las siguientes secciones, compilarás el resto de los elementos que admiten composición y los conectarás al ViewModel de manera similar.
8. Más migraciones de código XML
Ahora es más fácil completar lo que falta en nuestra IU: la información de riego y la descripción de las plantas. Si sigues el mismo enfoque de código de XML que realizaste antes, ya puedes migrar el resto de la pantalla.
El código XML de la información de riego que quitaste antes de fragment_plant_detail.xml consta de dos TextView con los ID plant_watering_header y plant_watering.
<TextView
android:id="@+id/plant_watering_header"
...
android:layout_marginStart="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_normal"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
android:text="@string/watering_needs_prefix"
android:textColor="?attr/colorAccent"
android:textStyle="bold"
... />
<TextView
android:id="@+id/plant_watering"
...
android:layout_marginStart="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_horizontal"
app:wateringText="@{viewModel.plant.wateringInterval}"
.../>
De manera similar a lo que hiciste antes, crea un nuevo elemento llamado PlantWatering y agrega Text para mostrar la información de riego en la pantalla:
PlantDetailDescription.kt
@Composable
private fun PlantWatering(wateringInterval: Int) {
Column(Modifier.fillMaxWidth()) {
// Same modifier used by both Texts
val centerWithPaddingModifier = Modifier
.padding(horizontal = dimensionResource(R.dimen.margin_small))
.align(Alignment.CenterHorizontally)
val normalPadding = dimensionResource(R.dimen.margin_normal)
Text(
text = stringResource(R.string.watering_needs_prefix),
color = MaterialTheme.colors.primaryVariant,
fontWeight = FontWeight.Bold,
modifier = centerWithPaddingModifier.padding(top = normalPadding)
)
val wateringIntervalText = LocalContext.current.resources.getQuantityString(
R.plurals.watering_needs_suffix, wateringInterval, wateringInterval
)
Text(
text = wateringIntervalText,
modifier = centerWithPaddingModifier.padding(bottom = normalPadding)
)
}
}
@Preview
@Composable
private fun PlantWateringPreview() {
MaterialTheme {
PlantWatering(7)
}
}
Con vista previa:

Debes tener en cuenta lo siguiente:
- A medida que los elementos
Textque admiten composición comparten el padding horizontal y la decoración de alineación, puedes volver a usar el Modificador asignándolo a una variable local (es decir,centerWithPaddingModifier). Esto es posible dado que los modificadores son objetos normales de Kotlin. - El
MaterialThemede Compose no tiene una concordancia exacta con elcolorAccentque se usa enplant_watering_header. Por ahora, usemosMaterialTheme.colors.primaryVariant, que mejorarás en la sección de temas.
Conectemos todas las piezas y llamemos a PlantWatering desde PlantDetailContent. El código XML ConstraintLayout que quitamos al principio tenía un margen de 16.dp que debemos incluir en nuestro código de Compose.
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/margin_normal">
En PlantDetailContent, crea un objeto Column a fin de mostrar el nombre y la información de riego juntos, y tener eso como padding. Además, para que el color de fondo y los colores de texto sean apropiados, agrega un Surface que se encargue de eso.
PlantDetailDescription.kt
@Composable
fun PlantDetailContent(plant: Plant) {
Surface {
Column(Modifier.padding(dimensionResource(R.dimen.margin_normal))) {
PlantName(plant.name)
PlantWatering(plant.wateringInterval)
}
}
}
Si actualiza la vista previa, verás lo siguiente:

9. Vistas en el código de Compose
Ahora, migraremos la descripción de las plantas. El código de fragment_plant_detail.xml tenía una TextView con app:renderHtml="@{viewModel.plant.description}" que le indicaba al XML qué texto mostrar en la pantalla. renderHtml es un adaptador de vinculación que puedes encontrar en el archivo PlantDetailBindingAdapters.kt. La implementación usa HtmlCompat.fromHtml para configurar el texto en TextView.
Sin embargo, Compose no admite clases Spanned ni muestra texto con formato HTML en este momento. Por lo tanto, necesitamos usar una TextView del sistema de View en el código de Compose a fin de evitar esta limitación.
Dado que Compose aún no puede renderizar código HTML, deberás crear una TextView de manera programática para hacer exactamente eso con la API de AndroidView.
AndroidView toma una View como parámetro y te proporciona una devolución de llamada para cuando se aumenta la Vista.
Para ello, creamos un nuevo elemento PlantDescription que admite composición. Este elemento llama a AndroidView con la TextView que acabamos de recordar en una lambda. En la devolución de llamada de factory, inicializa un TextView que reaccione a las interacciones HTML con el Context especificado. En la devolución de llamada de update, configura el texto con una descripción recordada en formato HTML.
PlantDetailDescription.kt
@Composable
private fun PlantDescription(description: String) {
// Remembers the HTML formatted description. Re-executes on a new description
val htmlDescription = remember(description) {
HtmlCompat.fromHtml(description, HtmlCompat.FROM_HTML_MODE_COMPACT)
}
// Displays the TextView on the screen and updates with the HTML description when inflated
// Updates to htmlDescription will make AndroidView recompose and update the text
AndroidView(
factory = { context ->
TextView(context).apply {
movementMethod = LinkMovementMethod.getInstance()
}
},
update = {
it.text = htmlDescription
}
)
}
@Preview
@Composable
private fun PlantDescriptionPreview() {
MaterialTheme {
PlantDescription("HTML<br><br>description")
}
}
Vista previa:

Observa que htmlDescription recuerda la descripción HTML de un elemento description determinado que se pasa como parámetro. Si cambia el parámetro description, se volverá a ejecutar el código htmlDescription dentro de remember.
De manera similar, si cambia htmlDescription, se recompondrá la devolución de llamada de actualización AndroidView. Cualquier estado que se lea dentro de la devolución de llamada provocará una recomposición.
Agreguemos PlantDescription al elemento PlantDetailContent que admite composición y cambiemos el código de la vista previa de modo que muestre una descripción HTML:
PlantDetailDescription.kt
@Composable
fun PlantDetailContent(plant: Plant) {
Surface {
Column(Modifier.padding(dimensionResource(R.dimen.margin_normal))) {
PlantName(plant.name)
PlantWatering(plant.wateringInterval)
PlantDescription(plant.description)
}
}
}
@Preview
@Composable
private fun PlantDetailContentPreview() {
val plant = Plant("id", "Apple", "HTML<br><br>description", 3, 30, "")
MaterialTheme {
PlantDetailContent(plant)
}
}
Con vista previa:

En este punto, ya migraste todo el contenido dentro del ConstraintLayout original a Compose. Puedes ejecutar la app para verificar que funcione según lo previsto.

10. ViewCompositionStrategy
De forma predeterminada, Compose elimina la composición cuando la ComposeView se separa de una ventana. Esto es un inconveniente cuando se usa ComposeView en fragmentos por varios motivos:
- La composición debe seguir el ciclo de vida de la vista del fragmento para los tipos de
Viewde la IU de Compose a fin de guardar el estado. - También debe hacerlo para mantener los elementos de la IU de Compose en la pantalla cuando se producen transiciones u ocurren transiciones de ventana. Durante las transiciones, la
ComposeViewen sí permanece visible incluso después de que se desconecta de la ventana.
Puedes llamar al método AbstractComposeView.disposeComposition con el fin de desechar la composición de forma manual. Como alternativa, para eliminar automáticamente composiciones cuando ya no sean necesarias, configura una estrategia diferente o crea una con llamadas al método setViewCompositionStrategy.
Usa la estrategia DisposeOnViewTreeLifecycleDestroyed a los efectos de eliminar la composición cuando se destruye el LifecycleOwner del fragmento.
Como PlantDetailFragment tiene transiciones de entrada y de salida (comprueba nav_garden.xml para obtener más información) y usaremos tipos de View de Compose más adelante, debemos asegurarnos de que ComposeView utilice la estrategia DisposeOnViewTreeLifecycleDestroyed. No obstante, configurar siempre esta estrategia cuando se use ComposeView en fragmentos es una práctica recomendada.
plantdetail/PlantDetailFragment.kt
import androidx.compose.ui.platform.ViewCompositionStrategy
...
class PlantDetailFragment : Fragment() {
...
override fun onCreateView(...): View? {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
...
composeView.apply {
// Dispose the Composition when the view's LifecycleOwner
// is destroyed
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
setContent {
MaterialTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
...
}
}
11. Temas de interoperabilidad
Se migró el contenido de texto de los detalles de las plantas a Compose. Sin embargo, habrás notado que Compose no usa los colores de tema correctos. Usa el color púrpura en el nombre de la planta cuando debería usar el verde.
En esta etapa de migración temprana, es recomendable que Compose herede los temas disponibles en el sistema de View, en lugar de volver a escribir tu propio tema de Material en Compose desde cero. Los temas de Material funcionan a la perfección con todos los componentes de Material Design que vienen con Compose.
Para volver a usar el tema de los componentes de Material Design (MDC) del sistema de View en Compose, puedes usar compose-theme-adapter. La función MdcTheme leerá automáticamente el tema de MDC del contexto de host y los pasará a MaterialTheme por ti para los temas claro y oscuro. Aunque solo necesitas los colores de tema para este codelab, la biblioteca también lee las formas y la tipografía del sistema de View.
La biblioteca ya se incluye en el archivo app/build.gradle de la siguiente manera:
...
dependencies {
...
implementation "com.google.android.material:compose-theme-adapter:$rootProject.composeVersion"
...
}
A fin de usar esto, reemplaza los usos de MaterialTheme para MdcTheme. Por ejemplo, en PlantDetailFragment.
PlantDetailFragment.kt
class PlantDetailFragment : Fragment() {
...
composeView.apply {
...
setContent {
MdcTheme {
PlantDetailDescription(plantDetailViewModel)
}
}
}
}
Y todos los elementos de vista previa que admiten composición en el archivo PlantDetailDescription.kt:
PlantDetailDescription.kt
@Preview
@Composable
private fun PlantDetailContentPreview() {
val plant = Plant("id", "Apple", "HTML<br><br>description", 3, 30, "")
MdcTheme {
PlantDetailContent(plant)
}
}
@Preview
@Composable
private fun PlantNamePreview() {
MdcTheme {
PlantName("Apple")
}
}
@Preview
@Composable
private fun PlantWateringPreview() {
MdcTheme {
PlantWatering(7)
}
}
@Preview
@Composable
private fun PlantDescriptionPreview() {
MdcTheme {
PlantDescription("HTML<br><br>description")
}
}
Como puedes ver en la vista previa, MdcTheme capta los colores del tema en el archivo styles.xml.

También puedes obtener una vista previa de la IU en tema oscuro creando una función nueva y pasando Configuration.UI_MODE_NIGHT_YES al uiMode de la vista previa:
import android.content.res.Configuration
...
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PlantDetailContentDarkPreview() {
val plant = Plant("id", "Apple", "HTML<br><br>description", 3, 30, "")
MdcTheme {
PlantDetailContent(plant)
}
}
Con vista previa:

Si ejecutas la app, esta se comportará exactamente igual que antes de la migración en el tema claro y oscuro:

12. Prueba
Después de migrar partes de la pantalla de detalles de las plantas a Compose, las pruebas son fundamentales para asegurarte de que no hayas dañado nada.
En Sunflower, el objeto PlantDetailFragmentTest ubicado en la carpeta androidTest prueba algunas funciones de la app. Abre el archivo y observa el código actual:
testPlantNameverifica el nombre de la planta en la pantalla.testShareTextIntentverifica que se active el intent correcto después de presionar el botón para compartir.
Cuando una actividad o un fragmento utilizan Compose, en lugar de ActivityScenarioRule, debes usar createAndroidComposeRule, que integra ActivityScenarioRule con un objeto ComposeTestRule que te permite probar el código de Compose.
En PlantDetailFragmentTest, reemplaza la regla ActivityScenarioRule de uso por createAndroidComposeRule. Cuando se necesite la regla de actividad para configurar la prueba, usa el atributo activityRule de createAndroidComposeRule de la siguiente manera:
@RunWith(AndroidJUnit4::class)
class PlantDetailFragmentTest {
@Rule
@JvmField
val composeTestRule = createAndroidComposeRule<GardenActivity>()
...
@Before
fun jumpToPlantDetailFragment() {
populateDatabase()
composeTestRule.activityRule.scenario.onActivity { gardenActivity ->
activity = gardenActivity
val bundle = Bundle().apply { putString("plantId", "malus-pumila") }
findNavController(activity, R.id.nav_host).navigate(R.id.plant_detail_fragment, bundle)
}
}
...
}
Si ejecutas las pruebas, testPlantName fallará. testPlantName verifica que haya una TextView en la pantalla. Sin embargo, migraste esa parte de la IU a Compose. Por lo tanto, en su lugar, debes usar aserciones de Compose:
@Test
fun testPlantName() {
composeTestRule.onNodeWithText("Apple").assertIsDisplayed()
}
Si ejecutas las pruebas, verás que todas son exitosas.

13. Felicitaciones
¡Felicitaciones! Completaste este codelab con éxito.
La rama compose del proyecto original de GitHub de Sunflower migra por completo la pantalla de detalles de las plantas a Compose. Además de lo que hiciste en este codelab, también simula el comportamiento del CollapsingToolbarLayout. Esto incluye lo siguiente:
- Carga de imágenes con Compose
- Animaciones
- Mejor control de las dimensiones
- Y mucho más
¿Qué sigue?
Consulta los otros codelabs sobre la ruta de aprendizaje de Compose:
Lecturas adicionales
- Código de migración de Jetpack Compose
- Guía para crear contenido en apps existentes
- Crane, una app de ejemplo, incorpora una MapView en Compose