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 los codelabs anteriores de esta lección, aprendiste cómo obtener datos sobre bienes raíces en Marte a partir de un servicio web y cómo crear un elemento RecyclerView
con un diseño de cuadrícula para cargar y mostrar imágenes a partir de esos datos. En este codelab, completarás la app de MarsRealEstate. Para ello, implementarás la capacidad de filtrar las propiedades de Marte según si están disponibles para comprar o alquilar. También puedes crear una vista de detalles para que, si el usuario presiona una foto de la propiedad en la vista general, vea una vista detallada con los detalles de esa propiedad.
Conocimientos que ya deberías tener
- Cómo crear y usar fragmentos
- Cómo navegar entre fragmentos y usar Safe Args (un complemento de Gradle) para pasar datos entre fragmentos
- Cómo usar componentes de arquitectura, incluidos modelos de vista, fábricas de modelos de vista, transformaciones y
LiveData
- Cómo recuperar datos codificados en JSON de un servicio web de REST y analizarlos en objetos de Kotlin con las bibliotecas Retrofit y Moshi
Qué aprenderás
- Cómo usar expresiones de vinculación complejas en los archivos de diseño
- Cómo realizar solicitudes de Retrofit a un servicio web con opciones de consulta
Actividades
- Modifica la app de MarsRealEstate para marcar las propiedades de Marte que están en venta (en comparación con las que están en alquiler) con un ícono de signo de dólar.
- Usa el menú de opciones en la página de descripción general para crear una solicitud de servicio web que filtre las propiedades de Marte por tipo.
- Crea un fragmento de detalle para una propiedad de Marte, conecta ese fragmento a la cuadrícula de descripción general con navegación y pasa los datos de propiedad a ese fragmento.
En este codelab (y codelabs relacionados), trabajarás con una app llamada MarsRealEstate, que muestra las propiedades a la venta en Marte. Esta 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. En codelabs anteriores, creaste un objeto RecyclerView
con un diseño de cuadrícula para todas las fotos de propiedades:
En esta versión de la app, trabajas con el tipo de propiedad (alquiler o compra) y agregas un ícono al diseño de cuadrícula a fin de marcar las propiedades que están a la venta:
Modificas el menú de opciones de la aplicación para filtrar la cuadrícula a fin de que muestre solo las propiedades en alquiler o en venta:
Por último, crearás una vista detallada para una propiedad individual. Luego, conectarás los íconos de la cuadrícula de descripción general a ese fragmento de detalle con la navegación:
Hasta ahora, la única parte de los datos de propiedad de Marte que usabas era la URL de la imagen de la propiedad. Sin embargo, los datos de propiedad, que definiste en la clase MarsProperty
, también incluyen un ID, un precio y un tipo (de alquiler o venta). Para refrescar tu memoria, aquí tienes un fragmento de los datos JSON que obtienes del servicio web:
{
"price":8000000,
"id":"424908",
"type":"rent",
"img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg"
},
En esta tarea, comenzarás a trabajar con el tipo de propiedad de Marte para agregar una imagen de signo de dólar a las propiedades de la página de descripción general que están en venta.
Paso 1: Actualiza MarsProperty para incluir el tipo
La clase MarsProperty
define la estructura de datos para cada propiedad proporcionada por el servicio web. En un codelab anterior, usaste la biblioteca Moshi para analizar la respuesta JSON sin procesar del servicio web de Marte en objetos de datos individuales MarsProperty
.
En este paso, agregarás algo de lógica a la clase MarsProperty
a fin de indicar si una propiedad se encuentra en alquiler o no (es decir, si el tipo es la string "rent"
o "buy"
). Usarás esta lógica en más de un lugar, por lo que es mejor incluirla aquí en la clase de datos que replicarla.
- Abre la app de MarsRealEstate del último codelab. (Puedes descargar MarsRealEstateGrid si no tienes la app).
- Abre
network/MarsProperty.kt
. Agrega un cuerpo a la definición de la claseMarsProperty
y un método get personalizado paraisRental
que muestretrue
si el objeto es del tipo"rent"
.
data class MarsProperty(
val id: String,
@Json(name = "img_src") val imgSrcUrl: String,
val type: String,
val price: Double) {
val isRental
get() = type == "rent"
}
Paso 2: Actualiza el diseño del elemento de la cuadrícula
Ahora, actualiza el diseño del elemento de la cuadrícula de imágenes para mostrar un elemento de diseño con signo de dólar solo en las imágenes de propiedades que están en venta:
Con las expresiones de vinculación de datos, puedes realizar esta prueba completamente en el diseño XML de los elementos de la cuadrícula.
- Abre
res/layout/grid_view_item.xml
. Este es el archivo de diseño de cada celda individual en el diseño de cuadrícula deRecyclerView
. Actualmente, el archivo solo contiene el elemento<ImageView>
de la imagen de la propiedad. - Dentro del elemento
<data>
, agrega un elemento<import>
para la claseView
. Usa importaciones cuando quieras usar componentes de una clase dentro de una expresión de vinculación de datos en un archivo de diseño. En este caso, usarás las constantesView.GONE
yView.VISIBLE
, por lo que necesitarás tener acceso a la claseView
.
<import type="android.view.View"/>
- Encierra toda la vista de la imagen con una
FrameLayout
a fin de apilar el elemento de diseño del signo de dólar sobre la imagen de propiedad.
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/mars_image"
...
</FrameLayout>
- Para
ImageView
, cambia el atributoandroid:layout_height
amatch_parent
a fin de llenar el nuevoFrameLayout
superior.
android:layout_height="match_parent"
- Agrega un segundo elemento
<ImageView>
justo debajo del primero, dentro de laFrameLayout
. Usa la definición que se muestra a continuación. Esta imagen aparece en la esquina inferior derecha del elemento de la cuadrícula, sobre la imagen de Marte, y usa el elemento de diseño definido enres/drawable/ic_for_sale_outline.xml
para el ícono de signo de dólar.
<ImageView
android:id="@+id/mars_property_type"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_gravity="bottom|end"
android:adjustViewBounds="true"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_for_sale_outline"
tools:src="@drawable/ic_for_sale_outline"/>
- Agrega el atributo
android:visibility
a la vista de imagenmars_property_type
. Usa una expresión de vinculación para probar el tipo de propiedad y asigna la visibilidad aView.GONE
(para un alquiler) oView.VISIBLE
(a una compra).
android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"
Hasta ahora, solo viste expresiones de vinculación en diseños que usan variables individuales definidas en el elemento <data>
. Las expresiones de vinculación son extremadamente potentes y te permiten realizar operaciones como pruebas y cálculos matemáticos por completo dentro de tu diseño XML. En este caso, usarás el operador ternario (?:
) para realizar una prueba (¿es este objeto un alquiler?). Proporciona un resultado para verdadero (oculta el ícono de signo de dólar con View.GONE
) y otro para falso (muestra ese ícono con View.VISIBLE
).
A continuación, se muestra el nuevo archivo grid_view_item.xml
completo:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View"/>
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/mars_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
android:padding="2dp"
app:imageUrl="@{property.imgSrcUrl}"
tools:src="@tools:sample/backgrounds/scenic"/>
<ImageView
android:id="@+id/mars_property_type"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_gravity="bottom|end"
android:adjustViewBounds="true"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_for_sale_outline"
android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"
tools:src="@drawable/ic_for_sale_outline"/>
</FrameLayout>
</layout>
- Compila y ejecuta la app, y ten en cuenta que las propiedades que no son alquileres tienen el ícono de signo de dólar.
Actualmente, tu app muestra todas las propiedades de Marte en la cuadrícula de descripción general. Si un usuario comprara una propiedad en alquiler en Marte, tener los íconos para indicar cuáles de las propiedades disponibles están a la venta sería útil, pero aún hay muchas propiedades por las que desplazarse en la página. En esta tarea, agregarás un menú de opciones al fragmento de descripción general que le permite al usuario mostrar solo los alquileres, solo las propiedades en venta o todos.
Una forma de realizar esta tarea es probar el tipo de cada MarsProperty
en la cuadrícula de descripción general y solo mostrar las propiedades coincidentes. Sin embargo, el servicio web de Marte real tiene un parámetro de búsqueda o una opción (llamado filter
) que te permite obtener solo las propiedades del tipo rent
o buy
. Puedes usar esta búsqueda de filtro con la URL del servicio web realestate
en un navegador como este:
https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buy
En esta tarea, modificarás la clase MarsApiService
para agregar una opción de consulta a la solicitud de servicio web con Retrofit. Luego, conectarás el menú de opciones para volver a descargar todos los datos de la propiedad de Marte mediante esa opción de consulta. Debido a que la respuesta que recibes del servicio web solo contiene las propiedades que te interesan, no necesitas cambiar la lógica de visualización de la cuadrícula de descripción general.
Paso 1: Actualiza el servicio de la API de Mars
Para cambiar la solicitud, debes volver a revisar la clase MarsApiService
que implementaste en el primer codelab de esta serie. Modificas la clase para proporcionar una API de filtrado.
- Abre
network/MarsApiService.kt
. Debajo de las importaciones, crea unenum
llamadoMarsApiFilter
para definir constantes que coincidan con los valores de búsqueda que espera el servicio web.
enum class MarsApiFilter(val value: String) {
SHOW_RENT("rent"),
SHOW_BUY("buy"),
SHOW_ALL("all") }
- Modifica el método
getProperties()
para tomar la entrada de string de la consulta de filtro y anotar esa entrada con@Query("filter")
, como se muestra a continuación.
Importaretrofit2.http.Query
cuando se te solicite.
La anotación@Query
le indica al métodogetProperties()
(y, por lo tanto, Retrofit) que realice la solicitud de servicio web con la opción de filtro. Cada vez que se llama agetProperties()
, la URL de la solicitud incluye la parte?filter=type
, que indica al servicio web que responda con los resultados que coincidan con esa consulta.
fun getProperties(@Query("filter") type: String):
Paso 2: Actualiza el modelo de vista de descripción general
Solicitas datos de MarsApiService
en el método getMarsRealEstateProperties()
en OverviewViewModel
. Ahora, debes actualizar esa solicitud para tomar el argumento del filtro.
- Abre
overview/OverviewViewModel.kt
. Verás errores en Android Studio debido a los cambios que realizaste en el paso anterior. AgregaMarsApiFilter
(la enumeración de valores de filtro posibles) como parámetro a la llamadagetMarsRealEstateProperties()
.
Importacom.example.android.marsrealestate.network.MarsApiFilter
cuando se te solicite.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
- Modifica la llamada a
getProperties()
en el servicio Retrofit para pasar esa consulta de filtro como una string.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
- En el bloque
init {}
, pasaMarsApiFilter.SHOW_ALL
como argumento agetMarsRealEstateProperties()
para mostrar todas las propiedades cuando se cargue la app por primera vez.
init {
getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
- Al final de la clase, agrega un método
updateFilter()
que tome un argumentoMarsApiFilter
y llame agetMarsRealEstateProperties()
con ese argumento.
fun updateFilter(filter: MarsApiFilter) {
getMarsRealEstateProperties(filter)
}
Paso 3: Conecta el fragmento al menú de opciones
El último paso es conectar el menú ampliado al fragmento para llamar a updateFilter()
en el modelo de vista cuando el usuario selecciona una opción del menú.
- Abre
res/menu/overflow_menu.xml
. La app de MarsRealEstate tiene un menú ampliado existente que ofrece las tres opciones disponibles: mostrar todas las propiedades, mostrar solo las propiedades de alquiler y mostrar solo las propiedades en venta.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/show_all_menu"
android:title="@string/show_all" />
<item
android:id="@+id/show_rent_menu"
android:title="@string/show_rent" />
<item
android:id="@+id/show_buy_menu"
android:title="@string/show_buy" />
</menu>
- Abre
overview/OverviewFragment.kt
. Al final de la clase, implementa el métodoonOptionsItemSelected()
para controlar las selecciones de elementos del menú.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
}
- En
onOptionsItemSelected()
, llama al métodoupdateFilter()
en el modelo de vista con el filtro apropiado. Usa un bloquewhen {}
de Kotlin para alternar entre las opciones. UsaMarsApiFilter.SHOW_ALL
para el valor de filtro predeterminado. Muestratrue
porque controlaste el elemento de menú. ImportaMarsApiFilter
(com.example.android.marsrealestate.network.MarsApiFilter
) cuando se solicite. A continuación, se muestra el métodoonOptionsItemSelected()
completo.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
viewModel.updateFilter(
when (item.itemId) {
R.id.show_rent_menu -> MarsApiFilter.SHOW_RENT
R.id.show_buy_menu -> MarsApiFilter.SHOW_BUY
else -> MarsApiFilter.SHOW_ALL
}
)
return true
}
- Compila y ejecuta la app, que iniciará la primera cuadrícula con todos los tipos de propiedades y las propiedades en venta marcadas con el ícono de dólar.
- Elige Alquilar en el menú de opciones. Las propiedades se vuelven a cargar y ninguna de ellas aparece con el ícono de dólar. (Solo se muestran las propiedades de alquiler). Es posible que deba esperar unos minutos para que se actualice la pantalla y muestre solo las propiedades filtradas.
- Elige Comprar en el menú de opciones. Las propiedades se vuelven a cargar, y todas aparecen con el ícono de dólar. (Solo se muestran las propiedades en venta).
Ahora tienes una cuadrícula de íconos para las propiedades de Marte, pero es hora de obtener más detalles. En esta tarea, agregarás un fragmento de detalle para mostrar los detalles de una propiedad específica. El fragmento de detalle mostrará una imagen más grande, el precio y el tipo de propiedad, ya sea un alquiler o una venta.
Este fragmento se inicia cuando el usuario presiona una imagen en la cuadrícula de descripción general. Para ello, debes agregar un objeto de escucha onClick
a los elementos de la cuadrícula RecyclerView
y, luego, navegar al fragmento nuevo. Para navegar, debes activar un cambio de LiveData
en ViewModel
, como lo hiciste en estas lecciones. También puedes usar el componente Safe Args de Navigation Component para pasar la información de MarsProperty
seleccionada del fragmento de descripción general al fragmento de detalles.
Paso 1: Crea el modelo de vista de detalles y actualiza el diseño de detalles
De manera similar al proceso que usaste para el fragmento y el modelo de vista general, ahora debes implementar el modelo de vista y los archivos de diseño para el fragmento de detalles.
- Abre
detail/DetailViewModel.kt
. Al igual que los archivos Kotlin relacionados con la red se encuentran en la carpetanetwork
y en los archivos de descripción general deoverview
, la carpetadetail
contiene los archivos asociados con la vista de detalles. Observa que la claseDetailViewModel
(en este momento, vacía) toma unmarsProperty
como parámetro en el constructor.
class DetailViewModel( marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
}
- Dentro de la definición de clase, agrega
LiveData
para la propiedad de Marte seleccionada, a fin de exponer esa información a la vista de detalles. Sigue el patrón habitual de creación de unMutableLiveData
para contener elMarsProperty
y, luego, expone una propiedad públicaLiveData
inmutable.
Importaandroidx.lifecycle.LiveData
y, luego, importaandroidx.lifecycle.MutableLiveData
cuando se solicite.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
get() = _selectedProperty
- Crea un bloque
init {}
y configura el valor de la propiedad de Marte seleccionada con el objetoMarsProperty
del constructor.
init {
_selectedProperty.value = marsProperty
}
- Abre
res/layout/fragment_detail.xml
y míralo en la vista de diseño.
Este es el archivo de diseño del fragmento de detalle. Contiene unImageView
para la foto grande, unTextView
para el tipo de propiedad (alquiler o oferta) y unTextView
para el precio. Ten en cuenta que el diseño de restricción se une con un elementoScrollView
a fin de que se desplace automáticamente si la vista se vuelve demasiado grande para la pantalla, por ejemplo, cuando el usuario la ve en el modo de paisaje. - Ve a la pestaña Text del diseño. En la parte superior del diseño, justo antes del elemento
<ScrollView>
, agrega un elemento<data>
para asociar el modelo de vista detallada con el diseño.
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
- Agrega el atributo
app:imageUrl
al elementoImageView
. Establécelo en elimgSrcUrl
de la propiedad seleccionada del modelo de vista.
El adaptador de vinculación que carga una imagen mediante Glide también se usará automáticamente aquí, ya que ese adaptador mira todos los atributosapp:imageUrl
.
app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"
Paso 2: Define la navegación en el modelo de vista de descripción general
Cuando el usuario presiona una foto en el modelo de descripción general, debería activar la navegación a un fragmento que muestre detalles del elemento en el que se hizo clic.
- Abre
overview/OverviewViewModel.kt
. Agrega una propiedad_navigateToSelectedProperty
MutableLiveData
y exponla con unLiveData
inmutable.
Cuando esteLiveData
cambia a no nulo, se activa la navegación. (Pronto usarás este código para observar esta variable y activar la navegación).
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
get() = _navigateToSelectedProperty
- Al final de la clase, agrega un método
displayPropertyDetails()
que establezca _navigateToSelectedProperty
en la propiedad de Marte seleccionada.
fun displayPropertyDetails(marsProperty: MarsProperty) {
_navigateToSelectedProperty.value = marsProperty
}
- Agrega un método
displayPropertyDetailsComplete()
que anule el valor de_navigateToSelectedProperty
. Lo necesitarás para marcar el estado de navegación como completado y evitar que se vuelva a activar cuando el usuario vuelva de la vista de detalles.
fun displayPropertyDetailsComplete() {
_navigateToSelectedProperty.value = null
}
Paso 3: Configura los objetos de escucha de clics en el fragmento y el adaptador de cuadrícula
- Abre
overview/PhotoGridAdapter.kt
. Al final de la clase, crea una claseOnClickListener
personalizada que tome una lambda con un parámetromarsProperty
. Dentro de la clase, define una funciónonClick()
que se establezca en el parámetro lambda.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
- Desplázate hasta la definición de clase de
PhotoGridAdapter
y agrega una propiedadOnClickListener
privada al constructor.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
- Para permitir que se pueda hacer clic en una foto, agrega
onClickListener
al elemento de la cuadrícula en el métodoonBindviewHolder()
. Define el objeto de escucha de clics entre las llamadas agetItem() and bind()
.
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
val marsProperty = getItem(position)
holder.itemView.setOnClickListener {
onClickListener.onClick(marsProperty)
}
holder.bind(marsProperty)
}
- Abre
overview/OverviewFragment.kt
. En el métodoonCreateView()
, reemplaza la línea que inicializa la propiedadbinding.photosGrid.adapter
por la que se muestra a continuación.
Este código agrega el objetoPhotoGridAdapter.onClickListener
al constructorPhotoGridAdapter
y llama aviewModel.displayPropertyDetails()
con el objetoMarsProperty
pasado. Esto activa elLiveData
en el modelo de vista de la navegación.
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
viewModel.displayPropertyDetails(it)
})
Paso 4: Modifica el gráfico de navegación y haz que MarsProperty sea parcelable
Cuando un usuario presiona una foto en la cuadrícula de descripción general, la app debe navegar al fragmento de detalles y pasar por los detalles de la propiedad de Marte seleccionada, de modo que la vista de detalles pueda mostrar esa información.
Ahora tienes un objeto de escucha de clics de PhotoGridAdapter
para controlar el toque, y una manera de activar la navegación desde el modelo de vista. Sin embargo, aún no pasas un objeto MarsProperty
al fragmento de detalle. Para ello, usa Safe Args del componente de navegación.
- Abre
res/navigation/nav_graph.xml
. Haz clic en la pestaña Text para ver el código XML del gráfico de navegación. - Dentro del elemento
<fragment>
para el fragmento de detalle, agrega el elemento<argument>
que se muestra a continuación. Este argumento, llamadoselectedProperty
, tiene el tipoMarsProperty
.
<argument
android:name="selectedProperty"
app:argType="com.example.android.marsrealestate.network.MarsProperty"
/>
- Compila la app. Navigation te mostrará un error porque
MarsProperty
no es parcelable. La interfazParcelable
permite que los objetos se serializan para que los datos se puedan pasar entre fragmentos o actividades. En este caso, para los datos dentro del objetoMarsProperty
que se pasarán al fragmento de detalle a través de Safe Args,MarsProperty
debe implementar la interfazParcelable
. La buena noticia es que Kotlin proporciona un acceso directo sencillo para implementar esa interfaz. - Abre
network/MarsProperty.kt
. Agrega la anotación@Parcelize
a la definición de la clase.
Importakotlinx.android.parcel.Parcelize
cuando se te solicite.
La anotación@Parcelize
usa las extensiones de Kotlin para Android a fin de implementar automáticamente los métodos en la interfazParcelable
de esta clase. No tienes que hacer nada más.
@Parcelize
data class MarsProperty (
- Cambia la definición de clase de
MarsProperty
para extenderParcelable
.
Importaandroid.os.Parcelable
cuando se te solicite.
La definición de la claseMarsProperty
ahora se ve de la siguiente manera:
@Parcelize
data class MarsProperty (
val id: String,
@Json(name = "img_src") val imgSrcUrl: String,
val type: String,
val price: Double) : Parcelable {
Paso 5: Conecta los fragmentos
Sigues sin navegar: la navegación real se produce en los fragmentos. En este paso, agregarás los últimos bits para implementar la navegación entre la descripción general y los fragmentos de detalles.
- Abre
overview/OverviewFragment.kt
. EnonCreateView()
, debajo de las líneas que inicializan el adaptador de cuadrícula de fotos, agrega las líneas que se muestran a continuación para observar elnavigatedToSelectedProperty
del modelo de vista general.
Importaandroidx.lifecycle.Observer
y, luego, importaandroidx.navigation.fragment.findNavController
cuando se solicite.
El observador prueba siMarsProperty
(elit
de la lambda) no es nulo y, de ser así, obtiene el controlador de navegación del fragmento confindNavController()
. Llama adisplayPropertyDetailsComplete()
para indicar al modelo de vista que restablezcaLiveData
al estado nulo, de modo que no vuelvas a activar accidentalmente la navegación cuando la app regrese aOverviewFragment
.
viewModel.navigateToSelectedProperty.observe(this, Observer {
if ( null != it ) {
this.findNavController().navigate(
OverviewFragmentDirections.actionShowDetail(it))
viewModel.displayPropertyDetailsComplete()
}
})
- Abre
detail/DetailFragment.kt
. Agrega esta línea justo debajo de la llamada asetLifecycleOwner()
en el métodoonCreateView()
. Esta línea obtiene el objetoMarsProperty
seleccionado de Safe Args.
Observa el uso del operador de aserción no nula de Kotlin (!!
). Si no se muestraselectedProperty
, ocurrió algo terrible y, en realidad, quieres que el código muestre un puntero nulo. (En el código de producción, debes controlar ese error de alguna manera).
val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
- Agrega esta línea a continuación para obtener un nuevo elemento
DetailViewModelFactory
. UsarásDetailViewModelFactory
para obtener una instancia delDetailViewModel
. La app de inicio incluye una implementación deDetailViewModelFactory
, por lo que todo lo que tienes que hacer aquí es inicializarla.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
- Por último, agrega esta línea para obtener un
DetailViewModel
de la fábrica y conectar todas las piezas.
binding.viewModel = ViewModelProviders.of(
this, viewModelFactory).get(DetailViewModel::class.java)
- Compila y ejecuta la app, y presiona cualquier foto de la propiedad de Marte. Aparecerá el fragmento de detalle para los detalles de esa propiedad. Presiona el botón Atrás para volver a la página de descripción general y observa que la pantalla de detalles aún está dispersa. Terminaste de agregar los datos de propiedad a esa página de detalles en la siguiente tarea.
Por el momento, en la página de detalles solo se muestra la misma foto de Marte que ves en la página de resumen. La clase MarsProperty
también tiene un tipo de propiedad (alquiler o compra) y un precio de propiedad. La pantalla de detalles debe incluir ambos valores. Sería útil si las propiedades de alquiler indicaran que el precio corresponde a un valor mensual. Debes usar transformaciones LiveData
en el modelo de vista para implementar ambos elementos.
- Abre
res/values/strings.xml
. El código de inicio incluye recursos de strings, que se muestran a continuación, para ayudarte a compilar las strings en la vista de detalles. En el caso del precio, usarás el recursodisplay_price_monthly_rental
odisplay_price
, según el tipo de propiedad.
<string name="type_rent">Rent</string>
<string name="type_sale">Sale</string>
<string name="display_type">For %s</string>
<string name="display_price_monthly_rental">$%,.0f/month</string>
<string name="display_price">$%,.0f</string>
- Abre
detail/DetailViewModel.kt
. En la parte inferior de la clase, agrega el código que se muestra a continuación.
Importaandroidx.lifecycle.Transformations
si se te solicita.
Esta transformación comprueba si la propiedad seleccionada es un alquiler mediante la misma prueba de la primera tarea. Si la propiedad es un alquiler, la transformación elige la string apropiada de los recursos con un interruptorwhen {}
de Kotlin. Ambas strings necesitan un número al final, por lo que debes concatenar el elementoproperty.price
después.
val displayPropertyPrice = Transformations.map(selectedProperty) {
app.applicationContext.getString(
when (it.isRental) {
true -> R.string.display_price_monthly_rental
false -> R.string.display_price
}, it.price)
}
- Importa la clase
R
generada para obtener acceso a los recursos de strings en el proyecto.
import com.example.android.marsrealestate.R
- Después de la transformación
displayPropertyPrice
, agrega el siguiente código. Esta transformación concatena múltiples recursos de strings, según si el tipo de propiedad es un alquiler.
val displayPropertyType = Transformations.map(selectedProperty) {
app.applicationContext.getString(R.string.display_type,
app.applicationContext.getString(
when (it.isRental) {
true -> R.string.type_rent
false -> R.string.type_sale
}))
}
- Abre
res/layout/fragment_detail.xml
. Solo hay que hacer algo más: vincular las nuevas strings (que creaste con las transformacionesLiveData
) a la vista de detalles. Para ello, establece el valor del campo de texto correspondiente al texto del tipo de propiedad enviewModel.displayPropertyType
y el campo de texto del valor del precio enviewModel.displayPropertyPrice
.
<TextView
android:id="@+id/property_type_text"
...
android:text="@{viewModel.displayPropertyType}"
...
tools:text="To Rent" />
<TextView
android:id="@+id/price_value_text"
...
android:text="@{viewModel.displayPropertyPrice}"
...
tools:text="$100,000" />
- Compila y ejecuta la app. Ahora, todos los datos de la propiedad aparecen en la página de detalles, con un formato adecuado.
Proyecto de Android Studio: MarsRealEstateFinal
Expresiones de vinculaciones
- Usa expresiones de vinculación en archivos de diseño XML para realizar operaciones programáticas simples, como pruebas matemáticas o condicionales, en datos vinculados.
- Para hacer referencia a clases dentro del archivo de diseño, usa la etiqueta
<import>
dentro de la etiqueta<data>
.
Opciones de búsqueda de servicio web
- Las solicitudes a los servicios web pueden incluir parámetros opcionales.
- Para especificar los parámetros de consulta en la solicitud, usa la anotación
@Query
en Retrofit.
Curso de Udacity:
Documentación para desarrolladores de Android:
- Descripción general de ViewModel
- Descripción general de LiveData
- Adaptadores de vinculación
- Diseños y expresiones vinculantes
- Navegación
- Comienza a usar el componente Navigation
- Pasar datos entre destinos (también describe Safe Args)
- Clase
Transformations
- Clase
ViewModelProvider
- Clase
ViewModelProvider.Factory
Otro:
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
¿Para qué sirve la etiqueta <import>
en un archivo de diseño XML?
▢ Incluye un archivo de diseño en otro.
▢ Incorpora código Kotlin en el archivo de diseño.
▢ Proporciona acceso a las propiedades vinculadas a datos.
▢ Permite hacer referencia a clases y miembros de clase en expresiones de vinculación.
Pregunta 2
¿Cómo se agrega una opción de consulta a una llamada de servicio web REST en Retrofit?
▢ Agregue la consulta al final de la URL de la solicitud.
▢ Agrega un parámetro para la consulta a la función que realiza la solicitud y anótalo con @Query
.
▢ Usa la clase Query
para compilar una solicitud.
▢ Usa el método addQuery()
en el compilador de Retrofit.
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.