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 los codelabs anteriores de esta lección, aprendiste a obtener datos sobre bienes raíces en Marte desde un servicio web y a crear un 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 implementando la capacidad de filtrar las propiedades de Marte según si están disponibles para alquilar o comprar. También crearás una vista de detalles para que, si el usuario presiona la foto de una propiedad en la vista general, vea una vista de detalles con información sobre 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 los modelos de vistas, los factores de modelos de vistas, las transformaciones y
LiveData - Cómo recuperar datos codificados en JSON de un servicio web de REST y analizar esos datos en objetos de Kotlin con las bibliotecas Retrofit y Moshi
Qué aprenderás
- Cómo usar expresiones de vinculación complejas en tus archivos de diseño
- Cómo realizar solicitudes de Retrofit a un servicio web con opciones de búsqueda
Actividades
- Modificarás la app de MarsRealEstate para marcar las propiedades de Marte que están a la 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 detalles para una propiedad de Marte, conecta ese fragmento a la cuadrícula de descripción general con navegación y pasa los datos de la propiedad a ese fragmento.
En este codelab (y en los codelabs relacionados), trabajarás con una app llamada MarsRealEstate, que muestra propiedades a la venta en Marte. Esta 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. En codelabs anteriores, creaste un RecyclerView con un diseño de cuadrícula para todas las fotos de las propiedades:

En esta versión de la app, trabajarás con el tipo de propiedad (alquiler o compra) y agregarás un ícono al diseño de cuadrícula para marcar las propiedades que están a la venta:

Modificas el menú de opciones de la app para filtrar la cuadrícula y mostrar solo las propiedades que están en alquiler o en venta:

Por último, crearás una vista de detalles para una propiedad individual y conectarás los íconos de la cuadrícula de resumen a ese fragmento de detalles con navegación:

Hasta ahora, la única parte de los datos de la propiedad de Marte que usaste es la URL de la imagen de la propiedad. Sin embargo, los datos de la propiedad, que definiste en la clase MarsProperty, también incluyen un ID, un precio y un tipo (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 en la página de descripción general que están a la venta.
Paso 1: Actualiza MarsProperty para incluir el tipo
La clase MarsProperty define la estructura de datos para cada propiedad que proporciona 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 MarsProperty individuales.
En este paso, agregarás lógica a la clase MarsProperty para indicar si una propiedad está en alquiler o no (es decir, si el tipo es la cadena "rent" o "buy"). Usarás esta lógica en más de un lugar, por lo que es mejor tenerla 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 claseMarsPropertyy agrega un getter personalizado paraisRentalque devuelvatruesi el objeto es de 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 cuadrícula
Ahora, actualiza el diseño del elemento para la cuadrícula de imágenes de modo que se muestre un elemento de diseño de signo de dólar solo en las imágenes de propiedades que estén a la venta:

Con las expresiones de vinculación de datos, puedes realizar esta prueba por completo 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 paraRecyclerView. Actualmente, el archivo solo contiene el elemento<ImageView>para la imagen de la propiedad. - Dentro del elemento
<data>, agrega un elemento<import>para la claseView. Usas importaciones cuando deseas 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.GONEyView.VISIBLE, por lo que necesitas acceder a la claseView.
<import type="android.view.View"/>- Rodea toda la vista de imagen con un
FrameLayoutpara permitir que el elemento de diseño de signo de dólar se apile sobre la imagen de la propiedad.
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/mars_image"
...
</FrameLayout>- Para el
ImageView, cambia el atributoandroid:layout_heightamatch_parentpara completar el nuevo elemento superiorFrameLayout.
android:layout_height="match_parent"- Agrega un segundo elemento
<ImageView>justo debajo del primero, dentro deFrameLayout. 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.xmlpara 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:visibilitya la vista de imagenmars_property_type. Usa una expresión de vinculación para probar el tipo de propiedad y asignar la visibilidad aView.GONE(para un alquiler) oView.VISIBLE(para 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 muy potentes y te permiten realizar operaciones como pruebas y cálculos matemáticos completamente dentro de tu diseño en XML. En este caso, usas el operador ternario (?:) para realizar una prueba (¿este objeto es un alquiler?). Proporcionas 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 observa que las propiedades que no son de alquiler tienen el ícono de signo de dólar.

Actualmente, tu app muestra todas las propiedades de Marte en la cuadrícula de resumen. Si un usuario estuviera buscando una propiedad de alquiler en Marte, sería útil tener los íconos para indicar cuáles de las propiedades disponibles están a la venta, 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 la vista general que le permita al usuario mostrar solo los alquileres, solo las propiedades en venta o mostrar todo.

Una forma de lograr esto es probar el tipo de cada MarsProperty en la cuadrícula de resumen y mostrar solo las propiedades coincidentes. Sin embargo, el servicio web de Marte real tiene un parámetro o una opción de búsqueda (llamado filter) que te permite obtener solo propiedades del tipo rent o del tipo buy. Podrías usar esta consulta de filtro con la URL del servicio web realestate en un navegador de la siguiente manera:
https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buyEn esta tarea, modificarás la clase MarsApiService para agregar una opción de consulta a la solicitud de servicio web con Retrofit. Luego, conectas el menú de opciones para volver a descargar todos los datos de las propiedades de Marte con esa opción de búsqueda. Dado que la respuesta que obtienes del servicio web solo contiene las propiedades que te interesan, no necesitas cambiar la lógica de visualización de la vista para la cuadrícula de resumen.
Paso 1: Actualiza el servicio de la API de Mars
Para cambiar la solicitud, debes volver a visitar 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. Justo debajo de las importaciones, crea unenumllamadoMarsApiFilterpara definir constantes que coincidan con los valores de consulta 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 que tome la entrada de cadena para la consulta de filtro y anota esa entrada con@Query("filter"), como se muestra a continuación.
Importaretrofit2.http.Querycuando se te solicite.
La anotación@Queryindica al métodogetProperties()(y, por lo tanto, a 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 resultados que coincidan con esa búsqueda.
fun getProperties(@Query("filter") type: String): Paso 2: Actualiza el modelo de vista general
Solicitas datos del MarsApiService en el método getMarsRealEstateProperties() en OverviewViewModel. Ahora debes actualizar esa solicitud para que tome el argumento de filtro.
- Abre
overview/OverviewViewModel.kt. Verás errores en Android Studio debido a los cambios que realizaste en el paso anterior. AgregaMarsApiFilter(el enum de los posibles valores de filtro) como parámetro a la llamadagetMarsRealEstateProperties().
Importacom.example.android.marsrealestate.network.MarsApiFiltercuando se te solicite.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {- Modifica la llamada a
getProperties()en el servicio de Retrofit para pasar esa consulta de filtro como una cadena.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)- En el bloque
init {}, pasaMarsApiFilter.SHOW_ALLcomo 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 argumentoMarsApiFiltery 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ú de desbordamiento al fragmento para llamar a updateFilter() en el ViewModel cuando el usuario elija una opción del menú.
- Abre
res/menu/overflow_menu.xml. La app de MarsRealEstate tiene un menú de desbordamiento existente que proporciona las tres opciones disponibles: mostrar todas las propiedades, mostrar solo las propiedades en 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 adecuado. Usa un bloquewhen {}de Kotlin para cambiar entre las opciones. UsaMarsApiFilter.SHOW_ALLpara el valor de filtro predeterminado. Devuelvetrue, ya que controlaste el elemento de menú. ImportaMarsApiFilter(com.example.android.marsrealestate.network.MarsApiFilter) cuando se te 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. La app lanza la primera cuadrícula de resumen 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 aparece con el ícono de dólar. (Solo se muestran las propiedades de alquiler). Es posible que debas esperar unos momentos 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 desplazable de íconos para las propiedades de Marte, pero es hora de obtener más detalles. En esta tarea, agregarás un fragmento de detalles para mostrar los detalles de una propiedad específica. El fragmento de detalles mostrará una imagen más grande, el precio y el tipo de propiedad, ya sea en alquiler o en venta.

Este fragmento se inicia cuando el usuario presiona una imagen en la cuadrícula de resumen. Para lograrlo, debes agregar un objeto de escucha onClick a los elementos de la cuadrícula RecyclerView y, luego, navegar al nuevo fragmento. Para navegar, activas un cambio de LiveData en el ViewModel, como lo hiciste a lo largo de estas lecciones. También usas el complemento Safe Args del componente Navigation para pasar la información de MarsProperty seleccionada del fragmento de resumen 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 modelo de vista y los fragmentos de la vista de resumen, 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 carpetanetworky los archivos de descripción general enoverview, la carpetadetailcontiene los archivos asociados con la vista de detalles. Observa que la claseDetailViewModel(vacía por el momento) toma unmarsPropertycomo parámetro en el constructor.
class DetailViewModel( marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
}- Dentro de la definición de la clase, agrega
LiveDatapara la propiedad de Marte seleccionada, de modo que se exponga esa información en la vista de detalles. Sigue el patrón habitual de crear unMutableLiveDatapara contener elMarsPropertyy, luego, exponer una propiedadLiveDatapública inmutable.
Importaandroidx.lifecycle.LiveDatayandroidx.lifecycle.MutableLiveDatacuando se te solicite.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
get() = _selectedProperty- Crea un bloque
init {}y establece el valor de la propiedad de Marte seleccionada con el objetoMarsPropertydel constructor.
init {
_selectedProperty.value = marsProperty
}- Abre
res/layout/fragment_detail.xmly míralo en la vista de diseño.
Este es el archivo de diseño del fragmento de detalles. Contiene unImageViewpara la foto grande, unTextViewpara el tipo de propiedad (alquiler o venta) y unTextViewpara el precio. Ten en cuenta que el diseño de restricción está envuelto en unScrollView, por lo que se desplazará automáticamente si la vista se vuelve demasiado grande para la pantalla, por ejemplo, cuando el usuario la ve en modo horizontal. - Ve a la pestaña Texto 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 de detalles con el diseño.
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>- Agrega el atributo
app:imageUrlal elementoImageView. Establécelo en elimgSrcUrlde la propiedad seleccionada del modelo de vista.
Aquí también se usará automáticamente el adaptador de vinculación que carga una imagen con Glide, ya que ese adaptador observa todos los atributosapp:imageUrl.
app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"Paso 2: Define la navegación en el ViewModel de la vista general
Cuando el usuario presiona una foto en el modelo de vista general, se debe activar la navegación a un fragmento que muestre detalles sobre el elemento en el que se hizo clic.
- Abre
overview/OverviewViewModel.kt. Agrega una propiedad_navigateToSelectedPropertyMutableLiveDatay exponla con unLiveDatainmutable.
Cuando esteLiveDatacambia a un valor no nulo, se activa la navegación. (Pronto agregarás el 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 _navigateToSelectedPropertyen la propiedad de Marte seleccionada.
fun displayPropertyDetails(marsProperty: MarsProperty) {
_navigateToSelectedProperty.value = marsProperty
}- Agrega un método
displayPropertyDetailsComplete()que anule el valor de_navigateToSelectedProperty. Necesitas esto para marcar el estado de navegación como completado y evitar que se vuelva a activar la navegación cuando el usuario regrese de la vista de detalles.
fun displayPropertyDetailsComplete() {
_navigateToSelectedProperty.value = null
}Paso 3: Configura los objetos de escucha de clics en el adaptador y el fragmento de la cuadrícula
- Abre
overview/PhotoGridAdapter.kt. Al final de la clase, crea una claseOnClickListenerpersonalizada que tome una expresión 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 hacia arriba hasta la definición de la clase
PhotoGridAdaptery agrega una propiedadOnClickListenerprivada al constructor.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {- Para que una foto sea apta para hacer clic, agrega
onClickListeneral 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.adaptercon la línea que se muestra a continuación.
Este código agrega el objetoPhotoGridAdapter.onClickListeneral constructorPhotoGridAdaptery llama aviewModel.displayPropertyDetails()con el objetoMarsPropertypasado. Esto activaLiveDataen el modelo de vista para 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 resumen, la app debe navegar al fragmento de detalles y pasar los detalles de la propiedad de Marte seleccionada para que la vista de detalles pueda mostrar esa información.

En este momento, tienes un objeto de escucha de clics de PhotoGridAdapter para controlar el toque y una forma de activar la navegación desde el ViewModel. Sin embargo, aún no tienes un objeto MarsProperty que se pase al fragmento de detalles. Para ello, usa Safe Args del componente de Navigation.
- 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>del fragmento de detalles, 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. La navegación te muestra un error porque
MarsPropertyno es parcelable. La interfazParcelablepermite serializar objetos para que sus datos se puedan pasar entre fragmentos o actividades. En este caso, para que los datos dentro del objetoMarsPropertyse pasen al fragmento de detalles a través de Safe Args,MarsPropertydebe implementar la interfazParcelable. La buena noticia es que Kotlin proporciona un atajo sencillo para implementar esa interfaz. - Abre
network/MarsProperty.kt. Agrega la anotación@Parcelizea la definición de la clase.
Importakotlinx.android.parcel.Parcelizecuando se solicite.
La anotación@Parcelizeusa las extensiones de Kotlin para Android para implementar automáticamente los métodos en la interfazParcelablepara esta clase. No es necesario que realices ninguna acción adicional.
@Parcelize
data class MarsProperty (- Cambia la definición de la clase
MarsPropertypara extenderParcelable.
Importaandroid.os.Parcelablecuando se te solicite.
La definición de la claseMarsPropertyahora 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
Aún no estás navegando. La navegación real ocurre en los fragmentos. En este paso, agregarás los últimos fragmentos para implementar la navegación entre los fragmentos de resumen y 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 elnavigatedToSelectedPropertydesde el modelo de vista de resumen.
Importaandroidx.lifecycle.Observeryandroidx.navigation.fragment.findNavControllercuando se te solicite.
El observador prueba siMarsProperty(eliten la expresión lambda) no es nulo y, si es así, obtiene el controlador de navegación del fragmento confindNavController(). Llama adisplayPropertyDetailsComplete()para indicarle al modelo de vista que restablezcaLiveDataal estado nulo, de modo que no vuelvas a activar la navegación por accidente cuando la app vuelva 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 objetoMarsPropertyseleccionado de Safe Args.
Observa el uso del operador de aserción no nulo de Kotlin (!!). Si no está elselectedProperty, ocurrió algo terrible y, en realidad, quieres que el código arroje un puntero nulo. (En el código de producción, debes controlar ese error de alguna manera).
val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty- A continuación, agrega esta línea para obtener un nuevo
DetailViewModelFactory. UsarásDetailViewModelFactorypara obtener una instancia deDetailViewModel. La app de partida incluye una implementación deDetailViewModelFactory, por lo que lo único que debes hacer aquí es inicializarla.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)- Por último, agrega esta línea para obtener un
DetailViewModelde la fábrica y conectar todas las partes.
binding.viewModel = ViewModelProviders.of(
this, viewModelFactory).get(DetailViewModel::class.java)- Compila y ejecuta la app, y presiona cualquier foto de propiedad de Marte. Aparecerá el fragmento de 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 sigue siendo un poco escasa. En la siguiente tarea, terminarás de agregar los datos de la propiedad a esa página de detalles.
Por el momento, la página de detalles solo muestra la misma foto de Marte que sueles ver en la página de descripción general. 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, y sería útil que las propiedades de alquiler indicaran que el precio es un valor por mes. Usas transformaciones LiveData en el modelo de vista para implementar ambas acciones.
- Abre
res/values/strings.xml. El código de partida incluye recursos de cadenas, que se muestran a continuación, para ayudarte a compilar las cadenas de la vista de detalles. Para el precio, usarás el recursodisplay_price_monthly_rentalo el recursodisplay_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.Transformationssi se te solicita.
Esta transformación prueba si la propiedad seleccionada es de alquiler, con la misma prueba de la primera tarea. Si la propiedad es un alquiler, la transformación elige la cadena adecuada de los recursos con un cambiowhen {}de Kotlin. Ambas cadenas necesitan un número al final, por lo que concatenasproperty.pricedespué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
Rgenerada para acceder a los recursos de cadena del proyecto.
import com.example.android.marsrealestate.R- Después de la transformación
displayPropertyPrice, agrega el código que se muestra a continuación. Esta transformación concatena varios recursos de cadena, según si el tipo de propiedad es de 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 queda una cosa más por hacer: vincular las cadenas nuevas (que creaste con las transformaciones deLiveData) a la vista de detalles. Para ello, establece el valor del campo de texto para el texto del tipo de propiedad enviewModel.displayPropertyTypey el campo de texto para el 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 agradable.

Proyecto de Android Studio: MarsRealEstateFinal
Expresiones de vinculación
- 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 de tu archivo de diseño, usa la etiqueta
<import>dentro de la etiqueta<data>.
Opciones de consulta de servicios web
- Las solicitudes a los servicios web pueden incluir parámetros opcionales.
- Para especificar parámetros de consulta en la solicitud, usa la anotación
@Queryen 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
- Cómo 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 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
¿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.
▢ Proporcionar acceso a 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?
▢ Agrega 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 anota ese parámetro con @Query.
▢ Usa la clase Query para compilar una solicitud.
▢ Usa el método addQuery() en el compilador de Retrofit.
Comienza la próxima 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.