Cómo cargar y mostrar imágenes de Internet

1. Antes de comenzar

Introducción

En codelabs anteriores, aprendiste a obtener datos de un servicio web usando un patrón de repositorio y a analizar la respuesta en un objeto Kotlin. En este codelab, aprenderás a cargar y mostrar fotos desde una URL web. También puedes revisar cómo compilar un objeto LazyVerticalGrid y usarlo para mostrar una cuadrícula de imágenes en la página de descripción general.

Requisitos previos

  • Conocimientos sobre cómo recuperar JSON de un servicio web de REST y analizar esos datos en objetos Kotlin mediante las bibliotecas Retrofit y Gson
  • Conocimientos de un servicio web REST
  • Familiarizarse con los componentes de la arquitectura de Android, como las capas de datos y los repositorios
  • Conocimientos sobre la inserción de dependencias
  • Conocimientos de ViewModel y ViewModelProvider.Factory
  • Conocimientos sobre la implementación de corrutinas para tu app
  • Conocimientos sobre el patrón de repositorio

Qué aprenderás

  • Cómo usar la biblioteca Coil para cargar y mostrar una imagen desde una URL web
  • Cómo usar una LazyVerticalGrid para mostrar una cuadrícula de imágenes
  • Cómo manejar los posibles errores mientras se descargan y se muestran las imágenes

Qué compilarás

  • Modificarás la app de Mars Photos para obtener la URL de la imagen de los datos de Marte y usarás Coil para cargar y mostrar esa imagen.
  • Agregarás una animación de carga y un ícono de error a la app.
  • Agregarás manejo de estado y errores a la app.

Qué necesitarás

  • Una computadora con un navegador web moderno, como la versión más reciente de Chrome
  • El código de inicio para la app de Mars Photos con servicios web de REST

2. Descripción general de la app

En este codelab, seguirás trabajando con la app de Mars Photos de un codelab anterior. La app de Mars Photos se conecta a un servicio web para recuperar y mostrar la cantidad de objetos de Kotlin recuperados con Gson. Estos objetos de Kotlin contienen las URLs de fotos reales de la superficie de Marte capturadas por los rovers de la NASA.

a59e55909b6e9213.png

La versión de la app que compiles en este codelab mostrará fotos de Marte en una cuadrícula de imágenes. Las imágenes son parte de los datos que tu app recupera del servicio web. Tu app usará la biblioteca de Coil para cargar y mostrar las imágenes, y un LazyVerticalGrid para crear el diseño de cuadrícula para las imágenes. Tu app también manejará los errores de red correctamente mostrando un mensaje de error.

68f4ff12cc1e2d81.png

Obtén el código de partida

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-mars-photos.git
$ cd basic-android-kotlin-compose-training-mars-photos
$ git checkout coil-starter

Puedes explorar el código en el repositorio de GitHub de Mars Photos.

3. Muestra una imagen descargada

Mostrar una foto de una URL web puede parecer sencillo, pero se necesita un poco de ingeniería para que funcione bien. La imagen se debe descargar, almacenar de forma interna (en caché) y decodificar de su formato comprimido a una imagen que Android pueda usar. Puedes almacenar la imagen en la caché de la memoria, en una caché basada en almacenamiento, o bien en ambas. Todo esto tiene que ocurrir en subprocesos en segundo plano de baja prioridad para que la IU siga siendo receptiva. Además, para obtener el mejor rendimiento de red y CPU, te recomendamos que recuperes y decodifiques más de una imagen a la vez.

Afortunadamente, puedes usar una biblioteca creada por la comunidad llamada Coil para descargar, almacenar en búfer, decodificar y almacenar en caché tus imágenes. Sin usar Coil, tendrías mucho más trabajo.

Básicamente, Coil necesita dos cosas:

  • La URL de la imagen que quieres cargar y mostrar
  • Un elemento de componibilidad AsyncImage para mostrar esa imagen

En esta tarea, aprenderás a utilizar Coil para mostrar una sola imagen del servicio web de Marte. Debes mostrar la imagen de la primera foto de Marte en la lista de fotos que muestra el servicio web. En las siguientes imágenes, se muestran las capturas de pantalla anterior y posterior:

a59e55909b6e9213.png 1b670f284109bbf5.png

Cómo agregar una dependencia de Coil

  1. Abre la app de Mars Photos solution del codelab Cómo agregar un repositorio y una DI manual.
  2. Ejecuta la app para confirmar que muestra el recuento de fotos de Marte recuperadas.
  3. Abre build.gradle.kts (Módulo :app).
  4. En la sección dependencies, agrega esta línea para la biblioteca de Coil:
// Coil
implementation("io.coil-kt:coil-compose:2.4.0")

Consulta la versión más reciente de la biblioteca y actualízala desde la página de documentación de Coil.

  1. Haz clic en Sync Now para volver a compilar el proyecto con la dependencia nueva.

Cómo mostrar la URL de la imagen

En este paso, recuperas y muestras la URL de la primera foto de Marte.

  1. En ui/screens/MarsViewModel.kt, dentro del método getMarsPhotos(), dentro del bloque try, busca la línea que establece los datos recuperados del servicio web como listResult.
// No need to copy, code is already present
try {
   val listResult = marsPhotosRepository.getMarsPhotos()
   //...
}
  1. Para actualizar esta línea, cambia listResult a result y asigna la primera foto de Marte recuperada a la variable nueva result. Asigna la primera foto en el índice 0.
try {
   val result = marsPhotosRepository.getMarsPhotos()[0]
   //...
}
  1. En la siguiente línea, actualiza el parámetro que se pasó a la llamada a función MarsUiState.Success() a la string en el siguiente código. Usa los datos de la propiedad nueva en lugar de listResult. Muestra la primera URL de imagen de la foto result.
try {
   ...
   MarsUiState.Success("First Mars image URL: ${result.imgSrc}")
}

El bloque try completo ahora tiene el siguiente aspecto:

marsUiState = try {
   val result = marsPhotosRepository.getMarsPhotos()[0]
   MarsUiState.Success(
       "   First Mars image URL : ${result.imgSrc}"
   )
}
  1. Ejecuta la app. Ahora el elemento de componibilidad Text muestra la URL de la primera foto de Marte. En la siguiente sección, se describe cómo hacer que la app muestre la imagen en esta URL.

b5daaa892fe8dad7.png

Agrega el elemento AsyncImage componible

En este paso, agregarás una función de componibilidad AsyncImage para cargar y mostrar una sola foto de Marte. AsyncImage es un elemento de componibilidad que ejecuta una solicitud de imagen de forma asíncrona y procesa el resultado.

// Example code, no need to copy over
AsyncImage(
    model = "https://android.com/sample_image.jpg",
    contentDescription = null
)

El argumento model puede ser el valor ImageRequest.data o el propio ImageRequest. En el ejemplo anterior, debes asignar el valor ImageRequest.data, es decir, la URL de la imagen, que es "https://android.com/sample_image.jpg". En el siguiente código de ejemplo, se muestra cómo asignar la ImageRequest al model.

// Example code, no need to copy over

AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data("https://example.com/image.jpg")
        .crossfade(true)
        .build(),
    placeholder = painterResource(R.drawable.placeholder),
    contentDescription = stringResource(R.string.description),
    contentScale = ContentScale.Crop,
    modifier = Modifier.clip(CircleShape)
)

AsyncImage admite los mismos argumentos que el elemento componible Image estándar. Además, admite la configuración de pintores placeholder/error/fallback y devoluciones de llamada onLoading/onSuccess/onError. El código de ejemplo anterior carga la imagen con un recorte circular y un encadenado, y establece un marcador de posición.

contentDescription establece el texto que usan los servicios de accesibilidad para describir lo que representa esta imagen.

Agrega un elemento de componibilidad AsyncImage a tu código para mostrar la primera foto de Marte recuperada.

  1. En ui/screens/HomeScreen.kt, agrega una nueva función que de componibilidad llamada MarsPhotoCard(), que toma MarsPhoto y Modifier.
@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {
}
  1. Dentro de la función de componibilidad MarsPhotoCard(), agrega la función AsyncImage() de la siguiente manera:
import coil.compose.AsyncImage
import coil.request.ImageRequest
import androidx.compose.ui.platform.LocalContext

@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {
    AsyncImage(
        model = ImageRequest.Builder(context = LocalContext.current)
            .data(photo.imgSrc)
            .build(),
        contentDescription = stringResource(R.string.mars_photo),
        modifier = Modifier.fillMaxWidth()
    )
}

En el código anterior, compilaste un ImageRequest con la URL de la imagen (photo.imgSrc) y lo pasaste al argumento model. Ahora usarás contentDescription a fin de configurar el texto para los lectores de accesibilidad.

  1. Agrega crossfade(true) a ImageRequest para habilitar una animación de encadenado cuando la solicitud se complete correctamente.
@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {
    AsyncImage(
        model = ImageRequest.Builder(context = LocalContext.current)
            .data(photo.imgSrc)
            .crossfade(true)
            .build(),
        contentDescription = stringResource(R.string.mars_photo),
        modifier = Modifier.fillMaxWidth()
    )
}
  1. Actualiza el elemento de componibilidad HomeScreen para mostrar el elemento de componibilidad MarsPhotoCard, en lugar de ResultScreen, cuando la solicitud se complete correctamente. Corregirás el error de discrepancia de tipos en el paso siguiente.
@Composable
fun HomeScreen(
    marsUiState: MarsUiState,
    modifier: Modifier = Modifier
) {
    when (marsUiState) {
        is MarsUiState.Loading -> LoadingScreen(modifier = modifier.fillMaxSize())
        is MarsUiState.Success -> MarsPhotoCard(photo = marsUiState.photos, modifier = modifier.fillMaxSize())
        else -> ErrorScreen(modifier = modifier.fillMaxSize())
    }
}
  1. En el archivo MarsViewModel.kt, actualiza la interfaz MarsUiState para que acepte un objeto MarsPhoto en lugar de un String.
sealed interface MarsUiState {
    data class Success(val photos: MarsPhoto) : MarsUiState
    //...
}
  1. Actualiza la función getMarsPhotos() para pasar el primer objeto de foto de Marte a MarsUiState.Success(). Borra la variable result.
marsUiState = try {
    MarsUiState.Success(marsPhotosRepository.getMarsPhotos()[0])
}
  1. Ejecuta la app y confirma que muestre una sola imagen de Marte.

d4421a2458f38695.png

  1. La foto de Marte no ocupa toda la pantalla. Para llenar el espacio disponible en pantalla, en HomeScreen.kt dentro de AsyncImage, establece contentScale en ContentScale.Crop.
import androidx.compose.ui.layout.ContentScale

@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {
   AsyncImage(
       model = ImageRequest.Builder(context = LocalContext.current)
           .data(photo.imgSrc)
           .crossfade(true)
           .build(),
       contentDescription = stringResource(R.string.mars_photo),
       contentScale = ContentScale.Crop,
       modifier = modifier,
   )
}
  1. Ejecuta la app y confirma que la imagen ocupe toda la pantalla tanto en horizontal como en vertical.

1b670f284109bbf5.png

Agrega imágenes de carga y error

Puedes mejorar la experiencia del usuario en tu app mostrando una imagen de marcador de posición mientras se carga la imagen. También puedes mostrar una imagen de error si la carga falla debido a un problema, como un archivo de imagen faltante o dañado. En esta sección, agregarás imágenes de error y marcador de posición con AsyncImage.

  1. Abre res/drawable/ic_broken_image.xml y haz clic en la pestaña Design o Split a la derecha. Para la imagen de error, usa el ícono de imagen rota que está disponible en la biblioteca de íconos integrada. Este elemento de diseño vectorial usa el atributo android:tint para colorear el ícono gris.

70e008c63a2a1139.png

  1. Abre res/drawable/loading_img.xml. Este elemento de diseño es una animación que rota un elemento de diseño de imagen, loading_img.xml, alrededor del punto central (no ves la animación en la vista previa).

92a448fa23b6d1df.png

  1. Regresa al archivo HomeScreen.kt. En el elemento de componibilidad MarsPhotoCard, actualiza la llamada a AsyncImage() para agregar los atributos error y placeholder, como se muestra en el siguiente código:
import androidx.compose.ui.res.painterResource

@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {
    AsyncImage(
        // ...
        error = painterResource(R.drawable.ic_broken_image),
        placeholder = painterResource(R.drawable.loading_img),
        // ...
    )
}

Este código configura el marcador de posición que se usará para cargar la imagen durante la carga (elemento de diseño loading_img). También configura la imagen que se usará si falla la carga (elemento de diseño ic_broken_image).

El elemento de componibilidad MarsPhotoCard completo ahora luce como el siguiente código:

@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {
    AsyncImage(
        model = ImageRequest.Builder(context = LocalContext.current)
            .data(photo.imgSrc)
            .crossfade(true)
            .build(),
        error = painterResource(R.drawable.ic_broken_image),
        placeholder = painterResource(R.drawable.loading_img),
        contentDescription = stringResource(R.string.mars_photo),
        contentScale = ContentScale.Crop
    )
}
  1. Ejecuta la app. Según la velocidad de la conexión de red, es posible que veas brevemente la imagen de carga mientras Coil descarga y muestra la imagen de la propiedad. Sin embargo, aún no verás el ícono de la imagen rota, incluso si desactivas la red; lo solucionarás en la última tarea del codelab.

d684b0e096e57643.gif

4. Muestra una cuadrícula de imágenes con RecyclerView

Ahora tu app carga una foto de Marte de Internet, el primer elemento de la lista de MarsPhoto. Usaste la URL de la imagen de esos datos de fotos de Marte para propagar un AsyncImage. Sin embargo, el objetivo es que tu app muestre una cuadrícula de imágenes. En esta tarea, usarás un LazyVerticalGrid con un administrador de diseño de cuadrícula para mostrar una cuadrícula de imágenes.

Cuadrículas diferidas

Los elementos de componibilidad LazyVerticalGrid y LazyHorizontalGrid admiten la visualización de elementos en una cuadrícula. Una cuadrícula vertical diferida muestra los elementos en un contenedor desplazable vertical, que abarca varias columnas, mientras que una cuadrícula horizontal diferida tiene el mismo comportamiento en el eje horizontal.

27680e208333ed5.png

Desde una perspectiva de diseño, Grid Layout es la mejor opción para mostrar fotos de Marte como íconos o imágenes.

El parámetro columns en LazyVerticalGrid y el parámetro rows en LazyHorizontalGrid controlan el modo en que se forman las celdas en columnas o filas. En el siguiente ejemplo, se muestran elementos de una cuadrícula con GridCells.Adaptive para que cada columna tenga al menos 128.dp ancho:

// Sample code - No need to copy over

@Composable
fun PhotoGrid(photos: List<Photo>) {
    LazyVerticalGrid(
        columns = GridCells.Adaptive(minSize = 150.dp)
    ) {
        items(photos) { photo ->
            PhotoItem(photo)
        }
    }
}

LazyVerticalGrid te permite especificar un ancho para los elementos, y la cuadrícula se ajusta a la mayor cantidad de columnas posible. Después de calcular la cantidad de columnas, la cuadrícula distribuye el ancho restante de manera equitativa entre ellas. Esta forma de tamaño adaptable es especialmente útil para mostrar conjuntos de elementos en diferentes tamaños de pantalla.

En este codelab, para mostrar fotos de Marte, usarás el elemento LazyVerticalGrid componible con GridCells.Adaptive, y cada columna configurada con un ancho de 150.dp.

Claves de elementos

Cuando el usuario se desplaza por la cuadrícula (una LazyRow dentro de una LazyColumn), cambia la posición del elemento de la lista. Sin embargo, en caso de cambio de orientación o si se agregan o quitan elementos, el usuario puede perder la posición de desplazamiento dentro de la fila. Las claves de elemento te ayudan a mantener la posición de desplazamiento en función de lo que indiquen.

Si proporcionas claves, ayudarás a Compose a controlar correctamente el reordenamiento. Por ejemplo, si tu elemento contiene un estado recordado, la configuración de claves permite a Compose mover este estado junto con el elemento cuando cambia su posición.

Agrega LazyVerticalGrid

Agrega un elemento componible para mostrar una lista de fotos de Marte en una cuadrícula vertical.

  1. En el archivo HomeScreen.kt, crea una nueva función de componibilidad llamada PhotosGridScreen(), que tome una lista de MarsPhoto y una modifier como argumentos.
@Composable
fun PhotosGridScreen(
    photos: List<MarsPhoto>,
    modifier: Modifier = Modifier,
    contentPadding: PaddingValues = PaddingValues(0.dp),
) {
}
  1. Dentro del elemento de componibilidad PhotosGridScreen, agrega un LazyVerticalGrid con los siguientes parámetros.
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.ui.unit.dp

@Composable
fun PhotosGridScreen(
    photos: List<MarsPhoto>,
    modifier: Modifier = Modifier,
    contentPadding: PaddingValues = PaddingValues(0.dp),
) {
    LazyVerticalGrid(
        columns = GridCells.Adaptive(150.dp),
        modifier = modifier.padding(horizontal = 4.dp),
        contentPadding = contentPadding,
   ) {
     }
}
  1. Para agregar una lista de elementos, dentro de la lambda LazyVerticalGrid, llama a la función items() pasando la lista de MarsPhoto y una clave de elemento como photo.id.
import androidx.compose.foundation.lazy.grid.items

@Composable
fun PhotosGridScreen(
    photos: List<MarsPhoto>,
    modifier: Modifier = Modifier,
    contentPadding: PaddingValues = PaddingValues(0.dp),
) {
   LazyVerticalGrid(
       // ...
   ) {
       items(items = photos, key = { photo -> photo.id }) {
       }
   }
}
  1. Para agregar el contenido que muestra un solo elemento de la lista, define la expresión lambda items. Llama a MarsPhotoCard y pasa la photo.
items(items = photos, key = { photo -> photo.id }) {
   photo -> MarsPhotoCard(photo)
}
  1. Actualiza el elemento de componibilidad HomeScreen para mostrar el elemento de componibilidad PhotosGridScreen, en lugar del MarsPhotoCard de componibilidad, al completar la solicitud de forma correcta.
when (marsUiState) {
       // ...
       is MarsUiState.Success -> PhotosGridScreen(marsUiState.photos, modifier)
       // ...
}
  1. En el archivo MarsViewModel.kt, actualiza la interfaz MarsUiState para que acepte una lista de objetos MarsPhoto, en lugar de una sola MarsPhoto. El elemento de componibilidad PhotosGridScreen acepta una lista de objetos MarsPhoto.
sealed interface MarsUiState {
    data class Success(val photos: List<MarsPhoto>) : MarsUiState
    //...
}
  1. En el archivo MarsViewModel.kt, actualiza la función getMarsPhotos() para pasar una lista de objetos de fotos de Marte a MarsUiState.Success().
marsUiState = try {
    MarsUiState.Success(marsPhotosRepository.getMarsPhotos())
}
  1. Ejecuta la app.

2eaec198c56b5eed.png

Ten en cuenta que no hay padding alrededor de cada foto, y la relación de aspecto es diferente para cada una de ellas. Puedes agregar un elemento de componibilidad Card para solucionar estos problemas.

Agrega un elemento componible de tarjeta

  1. En el archivo HomeScreen.kt, en el elemento MarsPhotoCard componible, agrega un elemento Card con la elevación 8.dp alrededor de la AsyncImage. Asigna el argumento modifier al elemento Card componible.
import androidx.compose.material.Card
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.padding

@Composable
fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) {

    Card(
        modifier = modifier,
        elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
    ) {

        AsyncImage(
            model = ImageRequest.Builder(context = LocalContext.current)
                .data(photo.imgSrc)
                .crossfade(true)
                .build(),
            error = painterResource(R.drawable.ic_broken_image),
            placeholder = painterResource(R.drawable.loading_img),
            contentDescription = stringResource(R.string.mars_photo),
            contentScale = ContentScale.Crop,
            modifier = Modifier.fillMaxWidth()
        )
    }
}
  1. Para corregir la relación de aspecto, en PhotosGridScreen(), actualiza el modificador de MarsPhotoCard().
@Composable
fun PhotosGridScreen(photos: List<MarsPhoto>, modifier: Modifier = Modifier) {
   LazyVerticalGrid(
       //...
   ) {
       items(items = photos, key = { photo -> photo.id }) { photo ->
           MarsPhotoCard(
               photo,
               modifier = modifier
                   .padding(4.dp)
                   .fillMaxWidth()
                   .aspectRatio(1.5f)
           )
       }
   }
}
  1. Actualiza la vista previa de la pantalla de resultados para obtener una vista previa de PhotosGridScreen(). Simula datos con URLs de imagen vacías.
@Preview(showBackground = true)
@Composable
fun PhotosGridScreenPreview() {
   MarsPhotosTheme {
       val mockData = List(10) { MarsPhoto("$it", "") }
       PhotosGridScreen(mockData)
   }
}

Dado que los datos ficticios tienen URLs vacías, verás que se cargan imágenes en la vista previa de la cuadrícula de fotos.

Vista previa de la pantalla de la cuadrícula de fotos con las imágenes cargándose

  1. Ejecuta la app.

b56acd074ce0f9c7.png

  1. Mientras se ejecuta la app, activa el modo de avión.
  2. Desplázate por las imágenes en el emulador. Las imágenes que aún no se cargaron se muestran como íconos de imágenes rotas. Este es el elemento de diseño de imagen que pasaste a la biblioteca de imágenes de Coil para mostrar en caso de que se produzca un error de red o no se pueda obtener la imagen.

9b72c1d4206c7331.png

¡Muy bien! Simulaste el error de conexión de red activando el modo de avión en el emulador o dispositivo.

5. Agrega la acción de volver a intentar

En esta sección, agregarás un botón para volver a intentar la acción y recuperarás las fotos cuando se haga clic en él.

60cdcd42bc540162.png

  1. Agrega un botón a la pantalla de error. En el archivo HomeScreen.kt, actualiza el elemento componible ErrorScreen() de modo que incluya un parámetro lambda retryAction y un botón.
@Composable
fun ErrorScreen(retryAction: () -> Unit, modifier: Modifier = Modifier) {
    Column(
        // ...
    ) {
        Image(
            // ...
        )
        Text(//...)
        Button(onClick = retryAction) {
            Text(stringResource(R.string.retry))
        }
    }
}

Cómo revisar la vista previa

55cf0c45f5be219f.png

  1. Actualiza el elemento HomeScreen() componible para pasar la lambda de reintento.
@Composable
fun HomeScreen(
   marsUiState: MarsUiState, retryAction: () -> Unit, modifier: Modifier = Modifier
) {
   when (marsUiState) {
       //...

       is MarsUiState.Error -> ErrorScreen(retryAction, modifier = modifier.fillMaxSize())
   }
}
  1. En el archivo ui/theme/MarsPhotosApp.kt, actualiza la llamada a función HomeScreen() para establecer el parámetro lambda retryAction en marsViewModel::getMarsPhotos. Esto recuperará las fotos de Marte del servidor.
HomeScreen(
   marsUiState = marsViewModel.marsUiState,
   retryAction = marsViewModel::getMarsPhotos
)

6. Actualiza la prueba de ViewModel

Ahora, MarsUiState y MarsViewModel admiten una lista de fotos, en lugar de una sola. En su estado actual, MarsViewModelTest espera que la clase de datos MarsUiState.Success contenga una propiedad de cadena. Por lo tanto, la prueba no se compila. Debes actualizar la prueba marsViewModel_getMarsPhotos_verifyMarsUiStateSuccess() para confirmar que MarsViewModel.marsUiState es igual al estado Success que contiene la lista de fotos.

  1. Abre el archivo rules/MarsViewModelTest.kt.
  2. En la prueba marsViewModel_getMarsPhotos_verifyMarsUiStateSuccess(), modifica la llamada a función assertEquals() para comparar un estado Success (pasando la lista de fotos falsas al parámetro de fotos) con el marsViewModel.marsUiState.
@Test
    fun marsViewModel_getMarsPhotos_verifyMarsUiStateSuccess() =
        runTest {
            val marsViewModel = MarsViewModel(
                marsPhotosRepository = FakeNetworkMarsPhotosRepository()
            )
            assertEquals(
                MarsUiState.Success(FakeDataSource.photosList),
                marsViewModel.marsUiState
            )
        }

La prueba ahora se compila, se ejecuta y se aprueba.

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

Para descargar el código del codelab terminado, puedes usar este comando de git:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos.git

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 para este codelab, míralo en GitHub.

8. Conclusión

Felicitaciones por completar este codelab y compilar la app de Mars Photos. Es hora de que alardees tu app con fotos reales de Marte con tu familia y amistades.

No olvides compartir tu trabajo en redes sociales con el hashtag #AndroidBasics.

9. Más información

Documentación para desarrolladores de Android:

Otra opción: