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
,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
- 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
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 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
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 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 delLiveData
para 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.value
en la cantidad de propiedades. Agrega la prueba que se muestra a continuación. Si hay objetosMarsProperty
disponibles, esta prueba establece el valor de_property
LiveData
en 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:text
para vincularlo al componenteimgSrcUrl
delproperty
LiveData
:
android:text="@{viewModel.property.imgSrcUrl}"
- Ejecuta la app. El elemento
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 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 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 se ejecute este adaptador de vinculación 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 cadena de URL (del XML) en un objetoUri
. Importaandroidx.core.net.toUri
cuando se te 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 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 objetoUri
en elImageView
. Importacom.bumptech.glide.Glide
cuando 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 deRecyclerView
má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: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 él más adelante.
//val binding = FragmentOverviewBinding.inflate(inflater)
- Agrega una línea para aumentar la clase
GridViewItemBinding
en su lugar. Importacom.example.android.marsrealestate. databinding.GridViewItemBinding
cuando se solicite.
val binding = GridViewItemBinding.inflate(inflater)
- Ejecuta la app. Ahora deberías ver la foto de la imagen del primer
MarsProperty
en 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.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. (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.RequestOptions
cuando 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
_property
a_properties
. Cambia el tipo para que sea una lista de objetosMarsProperty
.
private val _properties = MutableLiveData<List<MarsProperty>>()
- Reemplaza los datos en tiempo real externos
property
porproperties
. Agrega la lista al tipoLiveData
aquí 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 variablelistResult
contiene una lista de objetosMarsProperty
, puedes asignarla a_properties.value
en lugar de probar si la respuesta fue exitosa.
_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 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 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 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 unGridLayoutManager
y el diseñogrid_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 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
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 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
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
para 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 devuelvetrue
si las referencias de objeto paraoldItem
ynewItem
son 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 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 te solicite.
Necesitas la variableGridViewItemBinding
para vincularMarsProperty
al diseño, así que pasa la variable aMarsPropertyViewHolder
. Debido a que 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 devolver unMarsPropertyViewHolder
nuevo, creado inflando elGridViewItemBinding
y usando elLayoutInflater
de tu contexto deViewGroup
principal.
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ónRecyclerView
actual 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 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 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 unenum
para representar todos los estados disponibles:
enum class MarsApiStatus { LOADING, ERROR, DONE }
- Cambia el nombre de las definiciones de datos activos
_response
internas y externas en toda la claseOverviewViewModel
a_status
. Como agregaste compatibilidad con_properties
LiveData
anteriormente en este codelab, la respuesta completa del servicio web no se usó. Necesitas unLiveData
aquí 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_response
a_status
aquí también. Cambia la cadena"Success"
al estadoMarsApiStatus.DONE
y la cadena"Failure"
aMarsApiStatus.ERROR
. - Agrega un estado
MarsApiStatus.LOADING
en 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_properties
LiveData
en 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 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
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 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 laImageView
que se muestra a continuación.
EstaImageView
tiene las mismas restricciones que laRecyclerView
. 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 hace que la vista llame 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
- 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
ImageView
en 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
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 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.