Aspectos básicos de Kotlin para Android 08.3: Filtros 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 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.

  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 agrega un getter personalizado para isRental que devuelva true si 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.

  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 para RecyclerView. Actualmente, el archivo solo contiene el elemento <ImageView> para la imagen de la propiedad.
  2. Dentro del elemento <data>, agrega un elemento <import> para la clase View. 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 constantes View.GONE y View.VISIBLE, por lo que necesitas acceder a la clase View.
<import type="android.view.View"/>
  1. Rodea toda la vista de imagen con un FrameLayout para 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>
  1. Para el ImageView, cambia el atributo android:layout_height a match_parent para completar el nuevo elemento superior FrameLayout.
android:layout_height="match_parent"
  1. Agrega un segundo elemento <ImageView> justo debajo del primero, dentro de 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 asignar la visibilidad a View.GONE (para un alquiler) o View.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>
  1. 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=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, 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.

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

    Importa retrofit2.http.Query cuando se te solicite.

    La anotación @Query indica al método getProperties() (y, por lo tanto, a 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 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.

  1. Abre overview/OverviewViewModel.kt. Verás errores en Android Studio debido a los cambios que realizaste en el paso anterior. Agrega MarsApiFilter (el enum de los posibles valores de filtro) 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 de Retrofit para pasar esa consulta de filtro como una cadena.
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ú de desbordamiento al fragmento para llamar a updateFilter() en el ViewModel cuando el usuario elija una opción del menú.

  1. 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>
  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 adecuado. Usa un bloque when {} de Kotlin para cambiar entre las opciones. Usa MarsApiFilter.SHOW_ALL para el valor de filtro predeterminado. Devuelve true, ya que controlaste el elemento de menú. Importa MarsApiFilter (com.example.android.marsrealestate.network.MarsApiFilter) cuando se te 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. 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.
  2. 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.
  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 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.

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

    Importa androidx.lifecycle.LiveData y androidx.lifecycle.MutableLiveData cuando se te solicite.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. Crea un bloque init {} y establece 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 detalles. Contiene un ImageView para la foto grande, un TextView para el tipo de propiedad (alquiler o venta) y un TextView para el precio. Ten en cuenta que el diseño de restricción está envuelto en un ScrollView, 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.
  2. 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>
  1. Agrega el atributo app:imageUrl al elemento ImageView. Establécelo en el imgSrcUrl de 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 atributos app: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.

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

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

  1. Abre overview/PhotoGridAdapter.kt. Al final de la clase, crea una clase OnClickListener personalizada que tome una expresión 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 hacia arriba hasta la definición de la clase PhotoGridAdapter y agrega una propiedad OnClickListener privada al constructor.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. Para que una foto sea apta para hacer clic, 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 con la línea 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 LiveData en 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.

  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> del fragmento de detalles, 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. La navegación te muestra un error porque MarsProperty no es parcelable. La interfaz Parcelable permite serializar objetos para que sus datos se puedan pasar entre fragmentos o actividades. En este caso, para que los datos dentro del objeto MarsProperty se pasen al fragmento de detalles a través de Safe Args, MarsProperty debe implementar la interfaz Parcelable. La buena noticia es que Kotlin proporciona un atajo 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 solicite.

    La anotación @Parcelize usa las extensiones de Kotlin para Android para implementar automáticamente los métodos en la interfaz Parcelable para esta clase. No es necesario que realices ninguna acción adicional.
@Parcelize
data class MarsProperty (
  1. Cambia la definición de la clase 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

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.

  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 desde el modelo de vista de resumen.

    Importa androidx.lifecycle.Observer y androidx.navigation.fragment.findNavController cuando se te solicite.

    El observador prueba si MarsProperty (el it en la expresión lambda) no es nulo y, si es así, obtiene el controlador de navegación del fragmento con findNavController(). Llama a displayPropertyDetailsComplete() para indicarle al modelo de vista que restablezca LiveData al estado nulo, de modo que no vuelvas a activar la navegación por accidente cuando la app vuelva 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 nulo de Kotlin (!!). Si no está el selectedProperty, 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
  1. A continuación, agrega esta línea para obtener un nuevo DetailViewModelFactory. Usarás DetailViewModelFactory para obtener una instancia de DetailViewModel. La app de partida incluye una implementación de DetailViewModelFactory, por lo que lo único que debes 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 partes.
      binding.viewModel = ViewModelProviders.of(
                this, viewModelFactory).get(DetailViewModel::class.java)
  1. 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.

  1. 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 recurso display_price_monthly_rental o el recurso 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 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 cambio when {} de Kotlin. Ambas cadenas necesitan un número al final, por lo que concatenas 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 acceder a los recursos de cadena del proyecto.
import com.example.android.marsrealestate.R
  1. 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
                   }))
}
  1. Abre res/layout/fragment_detail.xml. Solo queda una cosa más por hacer: vincular las cadenas nuevas (que creaste con las transformaciones de LiveData) a la vista de detalles. Para ello, establece el valor del campo de texto para el texto del tipo de propiedad en viewModel.displayPropertyType y el campo de texto para el 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 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 @Query en Retrofit.

Curso de Udacity:

Documentación para desarrolladores de Android:

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

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.