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 en secuencia. Todos los codelabs del curso se detallan en la página de destino de codelabs sobre los aspectos básicos de Kotlin para Android.
Introducción
En el codelab anterior, aprendiste a obtener datos de un servicio web y 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 modelos de vista, fábricas de modelos de vista, 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
,ViewHolder
yDiffUtil
Qué aprenderás
- Cómo usar la biblioteca Glide para cargar y mostrar una imagen desde una URL web
- Cómo usar
RecyclerView
y 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
- Modifica la app de MarsRealEstate para obtener la URL de la imagen de los datos de la propiedad de Marte y usa Glide a fin de cargar y mostrar esa imagen.
- Agregarás una animación de carga y un ícono de error a la app.
- Usa un
RecyclerView
para 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 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 la propiedad, incluidos detalles como la propiedad y si se puede vender o alquilar. Las imágenes de cada propiedad son fotos reales de Marte capturadas por los rovers de la NASA.
La versión de la app que compilas 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 propiedad que tu app obtiene del servicio web de bienes raíces de Marte. Tu app usará la biblioteca 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 debe descargarse, almacenarse en búfer y decodificarse de su formato comprimido a una imagen que Android pueda utilizar. 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 forma efectiva desde la red 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 deja mucho menos trabajo que si tuvieras que hacer todo esto desde cero.
Básicamente, Glide necesita dos cosas:
- La URL de la imagen que deseas cargar y mostrar.
- Un objeto
ImageView
para 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 detalles de texto de una propiedad que está hipotéticamente 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 de 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, actualizarás la clase OverviewViewModel
para incluir datos en vivo de una sola propiedad de Marte.
- Abre
overview/OverviewViewModel.kt
. Debajo delLiveData
de la_response
, agrega datos en vivo internos (mutables) y externos (inmutables) para un solo objetoMarsProperty
.
Importa la claseMarsProperty
(com.example.android.marsrealestate.network.MarsProperty
) cuando se 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.value
en la cantidad de propiedades. Agrega la prueba que se muestra a continuación. Si los objetosMarsProperty
están disponibles, esta prueba establece el valor de_property
LiveData
en la primera propiedad enlistResult
.
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:text
para vincular al componenteimgSrcUrl
deproperty
LiveData
:
android:text="@{viewModel.property.imgSrcUrl}"
- Ejecuta la app.
TextView
solo 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 hora 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 la herramienta Glide para cargar la imagen. Los adaptadores de vinculación son métodos de extensión que se encuentran entre una vista y datos vinculados para proporcionar un comportamiento personalizado cuando los datos cambian. En este caso, el comportamiento personalizado es llamar a Glide para cargar una imagen desde una URL en 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 unImageView
y unString
como parámetros. Anota la función con@BindingAdapter
. La anotación@BindingAdapter
indica a la vinculación de datos que deseas que este adaptador de vinculación se ejecute cuando un elemento XML tenga el atributoimageUrl
.
Importaandroidx.databinding.BindingAdapter
yandroid.widget.ImageView
cuando 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 string de URL (del XML) en un objetoUri
. Importaandroidx.core.net.toUri
cuando se solicite.
Quieres que el objetoUri
final 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 ser parte de la claseString
.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
- Dentro de
let {}
, llama aGlide.with()
para cargar la imagen del objetoUri
aImageView
. Importacom.bumptech.glide.Glide
cuando se solicite.
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)
Paso 4: Actualiza el diseño y los fragmentos
A pesar de que Glide cargó la imagen, todavía no hay nada para 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 deRecyclerView
más adelante en el codelab. Se usa temporalmente 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:imageUrl
al elementoImageView
para 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 claseFragmentOverviewBinding
y la asigna a la variable de vinculación. Esto es solo temporal; volverás a verlo más tarde.
//val binding = FragmentOverviewBinding.inflate(inflater)
- En su lugar, agrega una línea para aumentar la clase
GridViewItemBinding
. Importacom.example.android.marsrealestate. databinding.GridViewItemBinding
cuando se solicite.
val binding = GridViewItemBinding.inflate(inflater)
- Ejecuta la app. Ahora deberías ver una foto de la primera imagen de la lista
MarsProperty
de resultados de la lista.
Paso 5: Agrega imágenes de error y carga simples
Glide puede mejorar la experiencia del usuario mostrando una imagen de marcador de posición mientras se carga la imagen y una imagen de error si falla la carga (por ejemplo, si 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.xml
y 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:tint
para 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. (La animación no se ve 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.RequestOptions
cuando se solicite.
Este código configura 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; debes solucionarlo en la última parte del codelab.
Ahora tu app carga información de propiedades desde Internet. Con los datos del primer elemento de lista MarsProperty
, creaste una propiedad LiveData
en el modelo de vista y usaste la URL de la imagen de esos datos de propiedad para propagar un ImageView
. Sin embargo, el objetivo es que tu app muestre una cuadrícula de imágenes, por lo que quieres usar una RecyclerView
con una GridLayoutManager
.
Paso 1: Actualiza el modelo de vista
En este momento, el modelo de vista tiene un _property
LiveData
que contiene un objeto MarsProperty
, el primero de 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
_property
a_properties
. Cambia el tipo para que sea una lista de objetosMarsProperty
.
private val _properties = MutableLiveData<List<MarsProperty>>()
- Reemplaza los datos activos
property
externos porproperties
. Agrega también la lista al tipoLiveData
:
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. Debido a que la variablelistResult
contiene una lista de objetosMarsProperty
, puedes asignarla a_properties.value
en lugar de probar una respuesta correcta.
_properties.value = listResult
El 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 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 deOverviewViewModel
aMarsProperty
y cambia el nombre de la variable a"property"
.
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
- En
<ImageView>
, cambia el atributoapp:imageUrl
para 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 última tarea.
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 un diseñoGridLayoutManager
ygrid_view_item
para 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 de fragment_overview
tiene un RecyclerView
, mientras que el de grid_view_item
tiene una sola ImageView
. En este paso, vinculas los datos a RecyclerView
a través de un adaptador RecyclerView
.
- Abre
overview/PhotoGridAdapter.kt
. - Crea la clase
PhotoGridAdapter
, con los parámetros del constructor que se muestran a continuación. La clasePhotoGridAdapter
extiendeListAdapter
, cuyo constructor necesita el tipo de elemento de lista, el contenedor de vistas y una implementaciónDiffUtil.ItemCallback
.
Importa las clasesandroidx.recyclerview.widget.ListAdapter
ycom.example.android.marsrealestate.network.MarsProperty
cuando se 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 parte de la clase
PhotoGridAdapter
y presionaControl+i
para 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.DiffUtil
cuando se te solicite.
El objetoDiffCallback
extiendeDiffUtil.ItemCallback
con el tipo de objeto que deseas comparar:MarsProperty
.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
- Presiona
Control+i
a fin de implementar los métodos del comparador para 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") }
- Para el método
areItemsTheSame()
, quita el comentario TODO. Usa el operador de igualdad referencial de Kotlin (===
), que muestratrue
si las referencias de objetos paraoldItem
ynewItem
son iguales.
override fun areItemsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem === newItem
}
- Para
areContentsTheSame()
, usa el operador de igualdad estándar solo en el ID deoldItem
ynewItem
.
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.RecyclerView
ycom.example.android.marsrealestate.databinding.GridViewItemBinding
cuando se solicite.
Necesitas la variableGridViewItemBinding
para vincularMarsProperty
al diseño, así que pasa la variable aMarsPropertyViewHolder
. Como la clase baseViewHolder
requiere 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 objetoMarsProperty
como argumento y establezcabinding.property
en 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.LayoutInflater
cuando se solicite.
El métodoonCreateViewHolder()
debe mostrar un nuevoMarsPropertyViewHolder
, creado mediante el aumento delGridViewItemBinding
y el uso delLayoutInflater
de tu contexto superiorViewGroup
.
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 objetoMarsProperty
asociado con la posición actualRecyclerView
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 BindingAdapter
para inicializar el 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 unRecyclerView
y una lista de objetosMarsProperty
como argumentos. Anota ese método con@BindingAdapter
.
Importaandroidx.recyclerview.widget.RecyclerView
ycom.example.android.marsrealestate.network.MarsProperty
cuando se te solicite.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsProperty>?) {
}
- Dentro de la función
bindRecyclerView()
, transmiterecyclerView.adapter
aPhotoGridAdapter
y llama aadapter.submitList()
con los datos. Esto le indica aRecyclerView
cuá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:listData
al elementoRecyclerView
y establécelo enviewmodel.properties
con la vinculación de datos.
app:listData="@{viewModel.properties}"
- Abre
overview/OverviewFragment.kt
. EnonCreateView()
, justo antes de la llamada asetHasOptionsMenu()
, inicializa el adaptadorRecyclerView
enbinding.photosGrid
en un objetoPhotoGridAdapter
nuevo.
binding.photosGrid.adapter = PhotoGridAdapter()
- Ejecuta la app. Deberías ver una cuadrícula de
MarsProperty
imágenes. 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 de avión, las imágenes que aún no se hayan cargado aparecerá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 gran experiencia del usuario. En esta tarea, agregarás administración básica de errores para darle al usuario una mejor idea 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 el estado al modelo de vista
Para comenzar, crea un LiveData
en el modelo de vista que represente 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 unenum
para representar todos los estados disponibles:
enum class MarsApiStatus { LOADING, ERROR, DONE }
- Cambia el nombre de las definiciones de datos en vivo internas y externas de
_response
a lo largo de la claseOverviewViewModel
a_status
. Como agregaste compatibilidad con elLiveData
de_properties
anteriormente en este codelab, no se usó la respuesta completa del servicio web. Aquí, necesitas unaLiveData
para hacer un seguimiento del estado actual a fin de poder 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_response
a_status
también aquí. Cambia la string"Success"
al estadoMarsApiStatus.DONE
y la string"Failure"
aMarsApiStatus.ERROR
. - Agrega un estado
MarsApiStatus.LOADING
a 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_properties
LiveData
en una lista vacía. Esta acción borrará 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 puedes hacer que aparezca en la misma app? En este paso, usarás un elemento ImageView
, conectado a la vinculación de datos, para mostrar íconos de los estados de carga y error. Cuando la app esté 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 valorImageView
y un valorMarsApiStatus
como argumentos. Importacom.example.android.marsrealestate.overview.MarsApiStatus
cuando se solicite.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}
- Agrega un elemento
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 elImageView
como 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.View
cuando 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 estadoImageView
como 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 estadoImageView
para ocultarla.
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 elImageView
que se muestra a continuación.
EsteImageView
tiene las mismas restricciones que elRecyclerView
. Sin embargo, el ancho y el alto usanwrap_content
para centrar la imagen en lugar de estirarla para llenar la vista. Observa también el atributoapp:marsApiStatus
, que tiene la vista de llamada a tuBindingAdapter
cuando 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
- Si quieres simplificar el proceso de administración de imágenes, usa la biblioteca Glide para descargar, almacenar en búfer, decodificar y almacenar en caché imágenes en tu app.
- Glide necesita dos cosas para cargar una imagen de Internet: la URL de una imagen y un objeto
ImageView
para colocarla. 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 de una URL a una
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 de carga yapply()
conerror()
para especificar un elemento de diseño con error. - Para producir una cuadrícula de imágenes, usa un
RecyclerView
con unGridLayoutManager
. - Para actualizar la lista de propiedades cuando cambie, usa un adaptador de vinculación entre
RecyclerView
y 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 tareas para los alumnos que trabajan con este codelab como parte de un curso que dicta un instructor. Depende del instructor hacer lo siguiente:
- Si es necesario, asigna la tarea.
- Informa a los alumnos cómo enviar los deberes.
- Califica las tareas.
Los instructores pueden usar estas sugerencias lo poco o lo que quieran, y deben asignar cualquier otra tarea que consideren apropiada.
Si estás trabajando en este codelab por tu cuenta, usa estas tareas para poner a prueba tus conocimientos.
Responda estas preguntas
Pregunta 1
¿Qué método de deslizamiento se usa para indicar el 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?
▢ Usa el método into()
con un elemento de diseño.
▢ Usa RequestOptions()
y llama al método placeholder()
con un elemento de diseño.
▢ Asigna la propiedad Glide.placeholder
a un elemento de diseño.
▢ Usa RequestOptions()
y llama 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 objeto 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
.
Comience la siguiente lección:
Para ver vínculos a otros codelabs de este curso, consulta la página de destino de codelabs sobre aspectos básicos de Kotlin para Android.