Aspectos básicos de Android Kotlin 08.3 Filtrado y vistas detalladas con datos de Internet

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.

  1. Abre la app de MarsRealEstate del último codelab. (Puedes descargar MarsRealEstateGrid si no tienes la app).
  2. Abre network/MarsProperty.kt. Agrega un cuerpo a la definición de la clase MarsProperty y un método get personalizado para isRental que muestre true 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.

  1. 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 de RecyclerView. Actualmente, el archivo solo contiene el elemento <ImageView> de la imagen de la propiedad.
  2. Dentro del elemento <data>, agrega un elemento <import> para la clase View. 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 constantes View.GONE y View.VISIBLE, por lo que necesitarás tener acceso a la clase View.
<import type="android.view.View"/>
  1. 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>
  1. Para ImageView, cambia el atributo android:layout_height a match_parent a fin de llenar el nuevo FrameLayout superior.
android:layout_height="match_parent"
  1. Agrega un segundo elemento <ImageView> justo debajo del primero, dentro de la FrameLayout. 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 en res/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"/>
  1. Agrega el atributo android:visibility a la vista de imagen mars_property_type. Usa una expresión de vinculación para probar el tipo de propiedad y asigna la visibilidad a View.GONE (para un alquiler) o View.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>
  1. 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.

  1. Abre network/MarsApiService.kt. Debajo de las importaciones, crea un enum llamado MarsApiFilter 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") }
  1. 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.

    Importa retrofit2.http.Query cuando se te solicite.

    La anotación @Query le indica al método getProperties() (y, por lo tanto, Retrofit) que realice la solicitud de servicio web con la opción de filtro. Cada vez que se llama a getProperties(), 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.

  1. Abre overview/OverviewViewModel.kt. Verás errores en Android Studio debido a los cambios que realizaste en el paso anterior. Agrega MarsApiFilter (la enumeración de valores de filtro posibles) como parámetro a la llamada getMarsRealEstateProperties().

    Importa com.example.android.marsrealestate.network.MarsApiFilter cuando se te solicite.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. 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)
  1. En el bloque init {}, pasa MarsApiFilter.SHOW_ALL como argumento a getMarsRealEstateProperties() para mostrar todas las propiedades cuando se cargue la app por primera vez.
init {
   getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
  1. Al final de la clase, agrega un método updateFilter() que tome un argumento MarsApiFilter y llame a getMarsRealEstateProperties() 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ú.

  1. 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>
  1. Abre overview/OverviewFragment.kt. Al final de la clase, implementa el método onOptionsItemSelected() para controlar las selecciones de elementos del menú.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. En onOptionsItemSelected(), llama al método updateFilter() en el modelo de vista con el filtro apropiado. Usa un bloque when {} de Kotlin para alternar entre las opciones. Usa MarsApiFilter.SHOW_ALL para el valor de filtro predeterminado. Muestra true porque controlaste el elemento de menú. Importa MarsApiFilter (com.example.android.marsrealestate.network.MarsApiFilter) cuando se solicite. A continuación, se muestra el método onOptionsItemSelected() 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
}
  1. 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.
  2. 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.
  3. 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.

  1. Abre detail/DetailViewModel.kt. Al igual que los archivos Kotlin relacionados con la red se encuentran en la carpeta network y en los archivos de descripción general de overview, la carpeta detail contiene los archivos asociados con la vista de detalles. Observa que la clase DetailViewModel (en este momento, vacía) toma un marsProperty como parámetro en el constructor.
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. 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 un MutableLiveData para contener el MarsProperty y, luego, expone una propiedad pública LiveData inmutable.

    Importa androidx.lifecycle.LiveData y, luego, importa androidx.lifecycle.MutableLiveData cuando se solicite.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. Crea un bloque init {} y configura el valor de la propiedad de Marte seleccionada con el objeto MarsProperty del constructor.
    init {
        _selectedProperty.value = marsProperty
    }
  1. 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 un ImageView para la foto grande, un TextView para el tipo de propiedad (alquiler o oferta) y un TextView para el precio. Ten en cuenta que el diseño de restricción se une con un elemento ScrollView 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.
  2. 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>
  1. Agrega el atributo app:imageUrl al elemento ImageView. Establécelo en el imgSrcUrl 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 atributos app: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.

  1. Abre overview/OverviewViewModel.kt. Agrega una propiedad _navigateToSelectedProperty MutableLiveData y exponla con un LiveData inmutable.

    Cuando este LiveData 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
  1. 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
}
  1. 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

  1. Abre overview/PhotoGridAdapter.kt. Al final de la clase, crea una clase OnClickListener personalizada que tome una lambda con un parámetro marsProperty. Dentro de la clase, define una función onClick() que se establezca en el parámetro lambda.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. Desplázate hasta la definición de clase de PhotoGridAdapter y agrega una propiedad OnClickListener privada al constructor.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. Para permitir que se pueda hacer clic en una foto, agrega onClickListener al elemento de la cuadrícula en el método onBindviewHolder(). Define el objeto de escucha de clics entre las llamadas a getItem() and bind().
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
   val marsProperty = getItem(position)
   holder.itemView.setOnClickListener {
       onClickListener.onClick(marsProperty)
   }
   holder.bind(marsProperty)
}
  1. Abre overview/OverviewFragment.kt. En el método onCreateView(), reemplaza la línea que inicializa la propiedad binding.photosGrid.adapter por la que se muestra a continuación.

    Este código agrega el objeto PhotoGridAdapter.onClickListener al constructor PhotoGridAdapter y llama a viewModel.displayPropertyDetails() con el objeto MarsProperty pasado. Esto activa el LiveData 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.

  1. 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.
  2. Dentro del elemento <fragment> para el fragmento de detalle, agrega el elemento <argument> que se muestra a continuación. Este argumento, llamado selectedProperty, tiene el tipo MarsProperty.
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. Compila la app. Navigation te mostrará un error porque MarsProperty no es parcelable. La interfaz Parcelable 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 objeto MarsProperty que se pasarán al fragmento de detalle a través de Safe Args, MarsProperty debe implementar la interfaz Parcelable. La buena noticia es que Kotlin proporciona un acceso directo sencillo para implementar esa interfaz.
  2. Abre network/MarsProperty.kt. Agrega la anotación @Parcelize a la definición de la clase.

    Importa kotlinx.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 interfaz Parcelable de esta clase. No tienes que hacer nada más.
@Parcelize
data class MarsProperty (
  1. Cambia la definición de clase de MarsProperty para extender Parcelable.

    Importa android.os.Parcelable cuando se te solicite.

    La definición de la clase MarsProperty 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.

  1. Abre overview/OverviewFragment.kt. En onCreateView(), 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 el navigatedToSelectedProperty del modelo de vista general.

    Importa androidx.lifecycle.Observer y, luego, importa androidx.navigation.fragment.findNavController cuando se solicite.

    El observador prueba si MarsProperty (el it de la lambda) no es nulo y, de ser así, obtiene el controlador de navegación del fragmento con findNavController(). Llama a displayPropertyDetailsComplete() para indicar al modelo de vista que restablezca LiveData al estado nulo, de modo que no vuelvas a activar accidentalmente la navegación cuando la app regrese a OverviewFragment.
viewModel.navigateToSelectedProperty.observe(this, Observer {
   if ( null != it ) {   
      this.findNavController().navigate(
              OverviewFragmentDirections.actionShowDetail(it))             
      viewModel.displayPropertyDetailsComplete()
   }
})
  1. Abre detail/DetailFragment.kt. Agrega esta línea justo debajo de la llamada a setLifecycleOwner() en el método onCreateView(). Esta línea obtiene el objeto MarsProperty seleccionado de Safe Args.

    Observa el uso del operador de aserción no nula de Kotlin (!!). Si no se muestra selectedProperty, 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
  1. Agrega esta línea a continuación para obtener un nuevo elemento DetailViewModelFactory. Usarás DetailViewModelFactory para obtener una instancia del DetailViewModel. La app de inicio incluye una implementación de DetailViewModelFactory, por lo que todo lo que tienes que hacer aquí es inicializarla.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. 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)
  1. 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.

  1. 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 recurso display_price_monthly_rental o display_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>
  1. Abre detail/DetailViewModel.kt. En la parte inferior de la clase, agrega el código que se muestra a continuación.

    Importa androidx.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 interruptor when {} de Kotlin. Ambas strings necesitan un número al final, por lo que debes concatenar el elemento property.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)
}
  1. Importa la clase R generada para obtener acceso a los recursos de strings en el proyecto.
import com.example.android.marsrealestate.R
  1. 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
                   }))
}
  1. Abre res/layout/fragment_detail.xml. Solo hay que hacer algo más: vincular las nuevas strings (que creaste con las transformaciones LiveData) a la vista de detalles. Para ello, establece el valor del campo de texto correspondiente al texto del tipo de propiedad en viewModel.displayPropertyType y el campo de texto del valor del precio en viewModel.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" />
  1. 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:

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: 9.1: Repositorio

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.