Este codelab es parte del curso Conceptos básicos de Kotlin para Android. Aprovecharás al máximo este curso si trabajas con los codelabs de forma secuencial. Todos los codelabs del curso se enumeran en la página de destino de los codelabs de Android Kotlin Fundamentals.
Introducción
En el codelab anterior, aprendiste a obtener datos de un servicio web y a analizar la respuesta en un objeto de datos. En este codelab, aprenderás a cargar y mostrar fotos desde una URL web. También puedes revisar cómo compilar un objeto RecyclerView y usarlo para mostrar una cuadrícula de imágenes en la página de descripción general.
Conocimientos que ya deberías tener
- Cómo crear y usar fragmentos
- Cómo usar componentes de arquitectura, incluidos los modelos de vistas, los factores de modelos de vistas, las transformaciones y
LiveData - Cómo recuperar JSON de un servicio web de REST y analizar esos datos en objetos de Kotlin con las bibliotecas Retrofit y Moshi
- Cómo construir un diseño de cuadrícula con
RecyclerView - Cómo funcionan
Adapter,ViewHolderyDiffUtil
Qué aprenderás
- Cómo usar la biblioteca Glide para cargar y mostrar una imagen desde una URL web
- Cómo usar
RecyclerViewy un adaptador de cuadrícula para mostrar una cuadrícula de imágenes - Cómo manejar los posibles errores mientras se descargan y se muestran las imágenes
Actividades
- Modificarás la app de MarsRealEstate para obtener la URL de la imagen de los datos de la propiedad de Marte y usarás Glide para cargar y mostrar esa imagen.
- Agregarás una animación de carga y un ícono de error a la app.
- Usarás
RecyclerViewpara mostrar una cuadrícula de imágenes de propiedades de Marte. - Agregarás administración de estado y errores a
RecyclerView.
En este codelab (y en los codelabs relacionados), trabajarás con una app llamada MarsRealEstate, que muestra las propiedades a la venta en Marte. La app se conecta a un servidor de Internet para recuperar y mostrar datos de propiedades, incluidos detalles como el precio y si la propiedad está disponible para la venta o el alquiler. Las imágenes que representan cada propiedad son fotografías reales de Marte capturadas por los rovers marcianos de la NASA.

La versión de la app que compiles en este codelab completará la página de descripción general, la cual muestra una cuadrícula de imágenes. Las imágenes son parte de los datos de la propiedad que tu app obtiene del servicio web de bienes raíces de Marte. Tu app usará la biblioteca de Glide para cargar y mostrar las imágenes, y un RecyclerView a fin de crear el diseño de cuadrícula para las imágenes. Además, la app manejará correctamente los errores de red.
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 en búfer y decodificar de su formato comprimido a una imagen que Android pueda usar. La imagen debe almacenarse en una memoria caché, en una caché basada en almacenamiento o 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 recuperar y decodificar más de una imagen a la vez. Aprender a cargar imágenes de la red de manera eficaz podría ser un codelab en sí mismo.
Afortunadamente, puedes usar una biblioteca desarrollada por la comunidad llamada Glide para descargar, almacenar en búfer, decodificar y almacenar en caché tus imágenes. Glide te ahorra mucho trabajo en comparación con si tuvieras que hacer todo esto desde cero.
Básicamente, Glide necesita dos cosas:
- Es la URL de la imagen que quieres cargar y mostrar.
- Un objeto
ImageViewpara mostrar esa imagen.
En esta tarea, aprenderás a usar Glide para mostrar una sola imagen del servicio web de bienes raíces. Debes mostrar la imagen que representa la primera propiedad de Marte en la lista de propiedades que muestra el servicio web. Estas son las capturas de pantalla de antes y después:


Paso 1: Agrega la dependencia de Glide
- Abre la app de MarsRealEstate del último codelab. (Puedes descargar MarsRealEstateNetwork aquí si no tienes la app).
- Ejecuta la app para ver qué hace. (Muestra los detalles de texto de una propiedad que, hipotéticamente, está disponible en Marte).
- Abre build.gradle (Module: app).
- En la sección
dependencies, agrega esta línea para la biblioteca de Glide:
implementation "com.github.bumptech.glide:glide:$version_glide"
Observa que el número de versión ya está definido por separado en el archivo Gradle del proyecto.
- Haz clic en Sync Now para volver a compilar el proyecto con la dependencia nueva.
Paso 2: Actualiza el modelo de vista
A continuación, actualiza la clase OverviewViewModel para incluir datos en vivo de una sola propiedad de Marte.
- Abre
overview/OverviewViewModel.kt. Justo debajo delLiveDatapara el_response, agrega datos dinámicos internos (mutables) y externos (inmutables) para un solo objetoMarsProperty.
Importa la claseMarsProperty(com.example.android.marsrealestate.network.MarsProperty) cuando se te solicite.
private val _property = MutableLiveData<MarsProperty>()
val property: LiveData<MarsProperty>
get() = _property- En el método
getMarsRealEstateProperties(), busca la línea dentro del bloquetry/catch {}que establece_response.valueen la cantidad de propiedades. Agrega la prueba que se muestra a continuación. Si hay objetosMarsPropertydisponibles, esta prueba establece el valor de_propertyLiveDataen la primera propiedad delistResult.
if (listResult.size > 0) {
_property.value = listResult[0]
}El bloque try/catch {} completo ahora tiene el siguiente aspecto:
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
if (listResult.size > 0) {
_property.value = listResult[0]
}
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}- Abre el archivo
res/layout/fragment_overview.xml. En el elemento<TextView>, cambiaandroid:textpara vincularlo al componenteimgSrcUrldelpropertyLiveData:
android:text="@{viewModel.property.imgSrcUrl}"- Ejecuta la app. El elemento
TextViewsolo muestra la URL de la imagen en la primera propiedad de Marte. Todo lo que hiciste hasta ahora es configurar el modelo de vista y los datos en vivo para esa URL.

Paso 3: Crea un adaptador de vinculación y llama a Glide
Ahora tienes la URL de una imagen para mostrar, y es momento de comenzar a trabajar con Glide para cargar esa imagen. En este paso, usarás un adaptador de vinculación para tomar la URL de un atributo XML asociado con un ImageView y usarás Glide para cargar la imagen. Los adaptadores de vinculación son métodos de extensión que se encuentran entre una vista y los datos vinculados para proporcionar un comportamiento personalizado cuando cambian los datos. En este caso, el comportamiento personalizado consiste en llamar a Glide para cargar una imagen desde una URL en un ImageView.
- Abre
BindingAdapters.kt. Este archivo contendrá los adaptadores de vinculación que usas en toda la app. - Crea una función
bindImage()que tome unImageViewy unStringcomo parámetros. Anota la función con@BindingAdapter. La anotación@BindingAdapterindica a la vinculación de datos que deseas que se ejecute este adaptador de vinculación cuando un elemento XML tenga el atributoimageUrl.
Importaandroidx.databinding.BindingAdapteryandroid.widget.ImageViewcuando se te solicite.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
}- Dentro de la función
bindImage(), agrega un bloquelet {}para el argumentoimgUrl:
imgUrl?.let {
}- Dentro del bloque
let {}, agrega la línea que se muestra a continuación para convertir la cadena de URL (del XML) en un objetoUri. Importaandroidx.core.net.toUricuando se te solicite.
Quieres que el objetoUrifinal use el esquema HTTPS, ya que el servidor del que extraes las imágenes requiere ese esquema. Para usar el esquema HTTPS, agregabuildUpon.scheme("https")al compilador detoUri. El métodotoUri()es una función de extensión de Kotlin de la biblioteca principal de Android KTX, por lo que parece que forma parte de la claseString.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()- Aún dentro de
let {}, llama aGlide.with()para cargar la imagen del objetoUrien elImageView. Importacom.bumptech.glide.Glidecuando se te solicite.
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)Paso 4: Actualiza el diseño y los fragmentos
Aunque Glide cargó la imagen, aún no hay nada que ver. El siguiente paso es actualizar el diseño y los fragmentos con un ImageView para mostrar la imagen.
- Abre
res/layout/gridview_item.xml. Este es el archivo de recursos de diseño que usarás para cada elemento deRecyclerViewmás adelante en el codelab. Aquí lo usas de forma temporal para mostrar solo la imagen. - Por encima del elemento
<ImageView>, agrega un elemento<data>para la vinculación de datos y realiza la vinculación a la claseOverviewViewModel:
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>- Agrega un atributo
app:imageUrlal elementoImageViewpara usar el nuevo adaptador de vinculación de carga de imágenes:
app:imageUrl="@{viewModel.property.imgSrcUrl}"- Abre
overview/OverviewFragment.kt. En el métodoonCreateView(), comenta la línea que aumenta la claseFragmentOverviewBindingy la asigna a la variable de vinculación. Esto es solo temporal; volverás a él más adelante.
//val binding = FragmentOverviewBinding.inflate(inflater)- Agrega una línea para aumentar la clase
GridViewItemBindingen su lugar. Importacom.example.android.marsrealestate. databinding.GridViewItemBindingcuando se solicite.
val binding = GridViewItemBinding.inflate(inflater)- Ejecuta la app. Ahora deberías ver la foto de la imagen del primer
MarsPropertyen la lista de resultados.
Paso 5: Agrega imágenes de carga y error simples
Glide puede mejorar la experiencia del usuario mostrando una imagen de marcador de posición mientras carga la imagen y una imagen de error si la carga falla, por ejemplo, si la imagen falta o está dañada. En este paso, agregarás esa funcionalidad al adaptador de vinculación y al diseño.
- Abre
res/drawable/ic_broken_image.xmly haz clic en la pestaña Preview de la derecha. Para la imagen de error, utiliza el ícono de imagen rota que se encuentra disponible en la biblioteca de íconos integrada. Este elemento de diseño vectorial usa el atributoandroid:tintpara colorear el ícono gris.

- Abre
res/drawable/loading_animation.xml. Este elemento de diseño es una animación que se define con la etiqueta<animate-rotate>. La animación rota un elemento de diseño de imagen,loading_img.xml, alrededor del punto central. (no ves la animación en la vista previa).

- Regresa al archivo
BindingAdapters.kt. En el métodobindImage(), actualiza la llamada aGlide.with()para llamar a la funciónapply()entreload()yinto(). Importacom.bumptech.glide.request.RequestOptionscuando se te solicite.
Este código establece la imagen de carga del marcador de posición para usarla durante la carga (elemento de diseñoloading_animation). El código también configura una imagen para usarla si falla la carga (elemento de diseñobroken_image). El métodobindImage()completo ahora tiene el siguiente aspecto:
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri =
imgUrl.toUri().buildUpon().scheme("https").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(RequestOptions()
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image))
.into(imgView)
}
}
- Ejecuta la app. Según la velocidad de la conexión de red, es posible que veas brevemente la imagen de carga mientras Glide 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 parte del codelab.
Ahora tu app carga la información de la propiedad desde Internet. Con los datos del primer elemento de lista MarsProperty, creaste una propiedad LiveData en el modelo de vista y utilizaste la URL de imagen de esos datos de propiedad para propagar ImageView. Sin embargo, el objetivo es que la app muestre una cuadrícula de imágenes, por lo que debes usar un RecyclerView con un GridLayoutManager.
Paso 1: Actualiza el modelo de vista
En este momento, el modelo de vistas tiene un _property LiveData que contiene un objeto MarsProperty, el primero en la lista de respuestas del servicio web. En este paso, cambiarás ese LiveData para conservar la lista completa de objetos MarsProperty.
- Abre
overview/OverviewViewModel.kt. - Cambia la variable privada
_propertya_properties. Cambia el tipo para que sea una lista de objetosMarsProperty.
private val _properties = MutableLiveData<List<MarsProperty>>()- Reemplaza los datos en tiempo real externos
propertyporproperties. Agrega la lista al tipoLiveDataaquí también:
val properties: LiveData<List<MarsProperty>>
get() = _properties- Desplázate hacia abajo hasta el método
getMarsRealEstateProperties(). Dentro del bloquetry {}, reemplaza toda la prueba que agregaste en la tarea anterior por la línea que se muestra a continuación. Como la variablelistResultcontiene una lista de objetosMarsProperty, puedes asignarla a_properties.valueen lugar de probar si la respuesta fue exitosa.
_properties.value = listResultEl bloque try/catch completo ahora tiene el siguiente aspecto:
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
_properties.value = listResult
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}Paso 2: Actualiza los diseños y los fragmentos
El siguiente paso es cambiar el diseño y los fragmentos de la app para usar una vista de reciclador y un diseño de cuadrícula, en lugar de la vista de imagen única.
- Abre
res/layout/gridview_item.xml. Cambia la vinculación de datos deOverviewViewModelaMarsPropertyy cambia el nombre de la variable a"property".
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />- En
<ImageView>, cambia el atributoapp:imageUrlpara hacer referencia a la URL de la imagen en el objetoMarsProperty:
app:imageUrl="@{property.imgSrcUrl}"- Abre
overview/OverviewFragment.kt. EnonCreateview(), quita el comentario de la línea que aumentaFragmentOverviewBinding. Borra o comenta la línea que aumentaGridViewBinding. Estos cambios revierten los cambios temporales que realizaste en la tarea anterior.
val binding = FragmentOverviewBinding.inflate(inflater)
// val binding = GridViewItemBinding.inflate(inflater)- Abre
res/layout/fragment_overview.xml. Borra todo el elemento<TextView> - En su lugar, agrega este elemento
<RecyclerView>, que usa unGridLayoutManagery el diseñogrid_view_itempara un solo elemento:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="6dp"
android:clipToPadding="false"
app:layoutManager=
"androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />Paso 3: Agrega el adaptador de cuadrícula de fotos
Ahora el diseño fragment_overview tiene un RecyclerView, mientras que el diseño grid_view_item tiene un solo ImageView. En este paso, vincularás los datos a RecyclerView a través de un adaptador RecyclerView.
- Abre
overview/PhotoGridAdapter.kt. - Crea la clase
PhotoGridAdaptercon los parámetros del constructor que se muestran a continuación. La clasePhotoGridAdapterextiendeListAdapter, cuyo constructor necesita el tipo de elemento de lista, el contenedor de vistas y una implementaciónDiffUtil.ItemCallback.
Importa las clasesandroidx.recyclerview.widget.ListAdapterycom.example.android.marsrealestate.network.MarsPropertycuando se te solicite. En los siguientes pasos, implementarás las otras partes faltantes de este constructor que producen errores.
class PhotoGridAdapter : ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
}- Haz clic en cualquier lugar de la clase
PhotoGridAdaptery presionaControl+ipara implementar los métodosListAdapter, que sononCreateViewHolder()yonBindViewHolder().
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
TODO("not implemented")
}
override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
TODO("not implemented")
}- Al final de la definición de la clase
PhotoGridAdapter, después de los métodos que acabas de agregar, agrega una definición de objeto complementario paraDiffCallback, como se muestra a continuación.
Importaandroidx.recyclerview.widget.DiffUtilcuando se te solicite.
El objetoDiffCallbackextiendeDiffUtil.ItemCallbackcon el tipo de objeto que deseas comparar:MarsProperty.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}- Presiona
Control+ipara implementar los métodos del comparador de este objeto, que sonareItemsTheSame()yareContentsTheSame().
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented")
}
override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented") }- En el método
areItemsTheSame(), quita el comentario TODO. Usa el operador de igualdad referencial de Kotlin (===), que devuelvetruesi las referencias de objeto paraoldItemynewItemson las mismas.
override fun areItemsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem === newItem
}- Para
areContentsTheSame(), usa el operador de igualdad estándar solo en el ID deoldItemynewItem.
override fun areContentsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}- Dentro de la clase
PhotoGridAdapter, debajo del objeto complementario, agrega una definición de clase interna paraMarsPropertyViewHolder, que extiendeRecyclerView.ViewHolder.
Importaandroidx.recyclerview.widget.RecyclerViewycom.example.android.marsrealestate.databinding.GridViewItemBindingcuando se te solicite.
Necesitas la variableGridViewItemBindingpara vincularMarsPropertyal diseño, así que pasa la variable aMarsPropertyViewHolder. Debido a que la clase baseViewHolderrequiere una vista en su constructor, le pasas la vista raíz de vinculación.
class MarsPropertyViewHolder(private var binding:
GridViewItemBinding):
RecyclerView.ViewHolder(binding.root) {
}- En
MarsPropertyViewHolder, crea un métodobind()que reciba un objetoMarsPropertycomo argumento y establezcabinding.propertyen ese objeto. Llama aexecutePendingBindings()después de configurar la propiedad, lo que hará que la actualización se ejecute de inmediato.
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
}- En
onCreateViewHolder(), quita el comentario TODO y agrega la línea que se muestra a continuación. Importaandroid.view.LayoutInflatercuando se solicite.
El métodoonCreateViewHolder()debe devolver unMarsPropertyViewHoldernuevo, creado inflando elGridViewItemBindingy usando elLayoutInflaterde tu contexto deViewGroupprincipal.
return MarsPropertyViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))- En el método
onBindViewHolder(), quita el comentario TODO y agrega las líneas que se muestran a continuación. Aquí llamas agetItem()para obtener el objetoMarsPropertyasociado con la posiciónRecyclerViewactual y, luego, pasas esa propiedad al métodobind()enMarsPropertyViewHolder.
val marsProperty = getItem(position)
holder.bind(marsProperty)Paso 4: Agrega el adaptador de vinculación y conecta las partes
Por último, usa un elemento BindingAdapter para inicializar PhotoGridAdapter con la lista de objetos MarsProperty. Si usas un BindingAdapter para configurar los datos de RecyclerView, la vinculación de datos observa automáticamente el LiveData para la lista de objetos MarsProperty. Luego, se llama al adaptador de vinculación automáticamente cuando cambia la lista MarsProperty.
- Abre
BindingAdapters.kt. - Al final del archivo, agrega un método
bindRecyclerView()que tome unRecyclerViewy una lista de objetosMarsPropertycomo argumentos. Anota ese método con@BindingAdapter.
Importaandroidx.recyclerview.widget.RecyclerViewycom.example.android.marsrealestate.network.MarsPropertycuando se te solicite.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsProperty>?) {
}- Dentro de la función
bindRecyclerView(), transmiterecyclerView.adapteraPhotoGridAdaptery llama aadapter.submitList()con los datos. Esto le indica aRecyclerViewcuándo hay una lista nueva disponible.
Importa com.example.android.marsrealestate.overview.PhotoGridAdapter cuando se solicite.
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)- Abre
res/layout/fragment_overview.xml. Agrega el atributoapp:listDataal elementoRecyclerViewy establécelo enviewmodel.propertiescon la vinculación de datos.
app:listData="@{viewModel.properties}"- Abre
overview/OverviewFragment.kt. EnonCreateView(), justo antes de la llamada asetHasOptionsMenu(), inicializa el adaptadorRecyclerViewenbinding.photosGriden un objetoPhotoGridAdapternuevo.
binding.photosGrid.adapter = PhotoGridAdapter()- Ejecuta la app. Deberías ver una cuadrícula de imágenes de
MarsProperty. Mientras te desplazas para ver imágenes nuevas, la app muestra el ícono de progreso de carga antes de mostrar la imagen. Si activas el modo avión, las imágenes que aún no se cargaron se mostrarán como íconos de imágenes rotas.

La app de MarsRealEstate muestra el ícono de la imagen rota cuando no se puede recuperar una imagen. Sin embargo, cuando no dispones de una red, la app muestra una pantalla en blanco.

Esta no es una buena experiencia del usuario. En esta tarea, agregarás una administración de errores básica para que el usuario tenga una idea más clara de lo que sucede. Si Internet no está disponible, la app mostrará el ícono de error de conexión. Mientras la app recupera la lista MarsProperty, mostrará la animación de carga.
Paso 1: Agrega estado al ViewModel
Para comenzar, crea un LiveData en el modelo de vista para representar el estado de la solicitud web. Hay tres estados que se deben considerar: carga, éxito y error. El estado de carga se produce mientras esperas los datos en la llamada a await().
- Abre
overview/OverviewViewModel.kt. En la parte superior del archivo (después de las importaciones, antes de la definición de clase), agrega unenumpara representar todos los estados disponibles:
enum class MarsApiStatus { LOADING, ERROR, DONE }- Cambia el nombre de las definiciones de datos activos
_responseinternas y externas en toda la claseOverviewViewModela_status. Como agregaste compatibilidad con_propertiesLiveDataanteriormente en este codelab, la respuesta completa del servicio web no se usó. Necesitas unLiveDataaquí para hacer un seguimiento del estado actual, por lo que puedes cambiar el nombre de las variables existentes.
Además, cambia los tipos de String a MarsApiStatus..
private val _status = MutableLiveData<MarsApiStatus>()
val status: LiveData<MarsApiStatus>
get() = _status- Desplázate hacia abajo hasta el método
getMarsRealEstateProperties()y actualiza_responsea_statusaquí también. Cambia la cadena"Success"al estadoMarsApiStatus.DONEy la cadena"Failure"aMarsApiStatus.ERROR. - Agrega un estado
MarsApiStatus.LOADINGen la parte superior del bloquetry {}, antes de la llamada aawait(). Es el estado inicial mientras se ejecuta la corrutina y esperas los datos. El bloquetry/catch {}completo ahora tiene el siguiente aspecto:
try {
_status.value = MarsApiStatus.LOADING
var listResult = getPropertiesDeferred.await()
_status.value = MarsApiStatus.DONE
_properties.value = listResult
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
}- Después del estado de error en el bloque
catch {}, establece el_propertiesLiveDataen una lista vacía. Esta acción borra elRecyclerView.
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}Paso 2: Agrega un adaptador de vinculación para el estado ImageView
Ahora tienes un estado en el modelo de vista, pero es solo un conjunto de estados. ¿Cómo haces para que aparezca en la app? En este paso, usarás un ImageView, conectado a la vinculación de datos, para mostrar íconos para los estados de carga y error. Cuando la app se encuentre en el estado de carga o de error, ImageView debería estar visible. Cuando la app termine de cargarse, el ImageView debería estar invisible.
- Abre
BindingAdapters.kt. Agrega un nuevo adaptador de vinculación llamadobindStatus()que toma un valorImageViewy un valorMarsApiStatuscomo argumentos. Importacom.example.android.marsrealestate.overview.MarsApiStatuscuando se solicite.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}- Agrega un
when {}dentro del métodobindStatus()para alternar entre los diferentes estados.
when (status) {
}- Dentro del
when {}, agrega un caso para el estado de carga (MarsApiStatus.LOADING). Para este estado, configura elImageViewcomo visible y asígnale la animación de carga. Este es el mismo elemento de diseño de animación que usaste para Glide en la tarea anterior. Importaandroid.view.Viewcuando se solicite.
when (status) {
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
}
}- Agrega un caso para el estado Error, que es
MarsApiStatus.ERROR. De manera similar a lo que hiciste para el estadoLOADING, configura el estadoImageViewcomo visible y reutiliza el elemento de diseño de error de conexión.
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}- Agrega un caso para el estado Done, que es
MarsApiStatus.DONE. Aquí tienes una respuesta correcta, así que desactiva la visibilidad del estadoImageViewpara ocultarlo.
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}Paso 3: Agrega el estado ImageView al diseño
- Abre
res/layout/fragment_overview.xml. Debajo del elementoRecyclerView, dentro deConstraintLayout, agrega laImageViewque se muestra a continuación.
EstaImageViewtiene las mismas restricciones que laRecyclerView. Sin embargo, el ancho y el alto usanwrap_contentpara centrar la imagen en lugar de estirarla para llenar la vista. Observa también el atributoapp:marsApiStatus, que hace que la vista llame a tuBindingAdaptercuando cambia la propiedad de estado en el modelo de vista.
<ImageView
android:id="@+id/status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:marsApiStatus="@{viewModel.status}" />- Activa el modo de avión en el emulador o dispositivo para simular una conexión de red faltante. Compila y ejecuta la app, y observa la imagen de error que aparece:

- Presiona el botón Back para cerrar la app y desactiva el modo de avión. Usa la pantalla de recientes para regresar a la app. Según la velocidad de tu conexión de red, es posible que veas un ícono giratorio de carga extremadamente breve cuando la app consulte el servicio web antes de que las imágenes comiencen a cargarse.
Proyecto de Android Studio: MarsRealEstateGrid
- Para simplificar el proceso de administración de imágenes, usa la biblioteca de Glide para descargar, almacenar en búfer, decodificar y almacenar en caché imágenes en tu app.
- Glide necesita dos elementos para cargar una imagen de Internet: la URL de una imagen y un objeto
ImageViewen el que colocar la imagen. Para especificar estas opciones, usa los métodosload()yinto()con Glide. - Los adaptadores de vinculación son métodos de extensión que se encuentran entre una vista y los datos vinculados de esa vista. Los adaptadores de vinculación proporcionan un comportamiento personalizado cuando cambian los datos, por ejemplo, para llamar a Glide a fin de cargar una imagen desde una URL en un
ImageView. - Los adaptadores de vinculación son métodos de extensión anotados con la anotación
@BindingAdapter. - Para agregar opciones a la solicitud de Glide, usa el método
apply(). Por ejemplo, usaapply()conplaceholder()para especificar un elemento de diseño cargando y usaapply()conerror()para especificar un elemento de diseño de error. - Para producir una cuadrícula de imágenes, usa un
RecyclerViewcon unGridLayoutManager. - Para actualizar la lista de propiedades cuando cambie, usa un adaptador de vinculación entre
RecyclerViewy el diseño.
Curso de Udacity:
Documentación para desarrolladores de Android:
- Descripción general de ViewModel
- Descripción general de LiveData
- Corrutinas, documentación oficial
- Adaptadores de vinculación
Otra:
En esta sección, se enumeran las posibles actividades para el hogar para los alumnos que trabajan en este codelab como parte de un curso dirigido por un instructor. Depende del instructor hacer lo siguiente:
- Si es necesario, asigna una tarea.
- Comunicarles a los alumnos cómo enviar las actividades para el hogar.
- Califica las actividades para el hogar.
Los instructores pueden usar estas sugerencias en la medida que quieran y deben asignar cualquier otra actividad para el hogar que consideren apropiada.
Si estás trabajando en este codelab por tu cuenta, usa estas actividades para el hogar para probar tus conocimientos.
Responde estas preguntas:
Pregunta 1
¿Qué método aplicable se usa para indicar el elemento ImageView que contendrá la imagen cargada?
▢ into()
▢ with()
▢ imageview()
▢ apply()
Pregunta 2
¿Cómo especifico una imagen de marcador de posición para mostrar cuando se carga el deslizamiento?
▢ Usando el método into() con un elemento de diseño
▢ Usando RequestOptions() y llamando al método placeholder() con un elemento de diseño
▢ Asigna la propiedad Glide.placeholder a un elemento de diseño.
▢ Usando RequestOptions() y llamando al método loadingImage() con un elemento de diseño
Pregunta 3
¿Cómo indicas que un método es un adaptador de vinculación?
▢ Llama al método setBindingAdapter() en el LiveData.
▢ Coloca el método en un archivo Kotlin llamado BindingAdapters.kt.
▢ Usa el atributo android:adapter en el diseño XML.
▢ Anota el método con @BindingAdapter.
Comienza la siguiente lección:
Para obtener vínculos a otros codelabs de este curso, consulta la página de destino de los codelabs de Conceptos básicos de Kotlin para Android.