Conceitos básicos do Kotlin para Android 08.3 Como filtrar e detalhar visualizações com dados da Internet

Este codelab faz parte do curso Conceitos básicos do Kotlin para Android. Você aproveitará mais o curso se fizer os codelabs em sequência. Todos os codelabs do curso estão listados na página de destino dos codelabs do curso Conceitos básicos do Kotlin para Android.

Introdução

Nos codelabs anteriores desta lição, você aprendeu como receber dados sobre imóveis em Marte com um serviço da Web e como criar um RecyclerView com um layout de grade para carregar e exibir imagens desses dados. Neste codelab, você concluirá o app MarsRealEstate implementando o recurso de filtrar as propriedades de Marte pelas que estão disponíveis para aluguel ou compra. Você também cria uma visualização detalhada para que, se o usuário tocar em uma foto da propriedade na visão geral, ele tenha uma visualização detalhada com detalhes sobre a propriedade.

O que você já precisa saber

  • Como criar e usar fragmentos.
  • Como navegar entre fragmentos e usar o Safe Args (um plug-in do Gradle) para transmitir dados entre fragmentos.
  • Como usar componentes de arquitetura, incluindo modelos de visualização, fábricas de modelo de visualização, transformações e LiveData.
  • Saber como recuperar dados codificados JSON em um serviço REST da Web e analisá-los em objetos Kotlin com as bibliotecas Retrofit e Moshi (links em inglês).

O que você aprenderá

  • Como usar expressões de vinculação complexas nos arquivos de layout.
  • Como fazer solicitações à Retrofit para um serviço da Web com opções de consulta.

O que você aprenderá

  • Modifique o app MarsRealEstate para marcar as propriedades de Marte que estão à venda (em vez de propriedades para aluguel) com um ícone de cifrão.
  • Use o menu de opções na página de visão geral para criar uma solicitação de serviço da Web que filtre as propriedades de Marte por tipo.
  • Criar um fragmento de detalhes para uma propriedade de Marte, conectar esse fragmento à grade de visão geral com a navegação e transmitir os dados da propriedade para esse fragmento.

Neste codelab (e nos codelabs relacionados), você trabalhará com um app chamado MarsRealEstate, que mostra propriedades à venda em Marte. Esse app se conecta a um servidor de Internet para recuperar e exibir dados da propriedade, incluindo detalhes como preço e disponibilidade da propriedade. As imagens que representam cada propriedade são fotos reais de Marte capturadas de rovers da NASA em Marte. Nos codelabs anteriores, você criou uma RecyclerView com um layout de grade para todas as fotos da propriedade:

Nesta versão do app, você trabalha com o tipo da propriedade (aluguel x compra) e adiciona um ícone ao layout de grade para marcar as propriedades que estão à venda:

Você modifica o menu de opções do aplicativo para filtrar a grade e mostrar somente as propriedades que são para aluguel ou venda:

Por fim, você cria uma visualização detalhada para uma propriedade individual e conecta os ícones na grade de visão geral a esse fragmento com a navegação:

Até agora, a única parte dos dados da propriedade de Marte que você usou é o URL da imagem da propriedade. No entanto, os dados de propriedade, que você definiu na classe MarsProperty, também incluem um código, um preço e um tipo (aluguel ou venda). Para atualizar a memória, veja um snippet dos dados JSON recebidos do serviço da Web:

{
   "price":8000000,
   "id":"424908",
   "type":"rent",
   "img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg"
},

Nesta tarefa, você começará a trabalhar com o tipo de propriedade de Marte para adicionar uma imagem de cifrão às propriedades na página de visão geral que estão à venda.

Etapa 1: atualizar o MarsProperty para incluir o tipo

A classe MarsProperty define a estrutura de dados para cada propriedade fornecida pelo serviço da Web. Em um codelab anterior, você usou a biblioteca Moshi (link em inglês) para analisar a resposta JSON bruta do serviço da Web de Marte em objetos de dados MarsProperty individuais.

Nesta etapa, você adicionará uma lógica à classe MarsProperty para indicar se uma propriedade é para locação ou não, ou seja, se o tipo é a string "rent" ou "buy". Você usará essa lógica em mais de um lugar. Portanto, é melhor tê-la aqui na classe de dados do que replicá-la.

  1. Abra o app MarsRealEstate do último codelab. Se você não tiver o app MarsRealEstateGrid, poderá fazer o download dele.
  2. Abra o network/MarsProperty.kt Adicione um corpo à definição da classe MarsProperty e adicione um getter personalizado para isRental que retorne true se o objeto for do 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"
}

Etapa 2: atualizar o layout do item de grade

Agora, atualize o layout do item da grade de imagens para mostrar um drawable de cifrão apenas nas imagens de propriedade que estão à venda:

Com expressões de vinculação de dados, você pode fazer esse teste totalmente no layout XML dos itens da grade.

  1. Abra o res/layout/grid_view_item.xml Esse é o arquivo de layout de cada célula no layout de grade para a RecyclerView. No momento, o arquivo contém apenas o elemento <ImageView> da imagem da propriedade.
  2. No elemento <data>, adicione um elemento <import> para a classe View. Use importações quando quiser usar componentes de uma classe em uma expressão de vinculação de dados em um arquivo de layout. Nesse caso, você usará as constantes View.GONE e View.VISIBLE. Portanto, precisa acessar a classe View.
<import type="android.view.View"/>
  1. Cerque toda a visualização de imagem com um FrameLayout para permitir que o drawable de cifrão seja empilhado sobre a imagem da propriedade.
<FrameLayout
   android:layout_width="match_parent"
   android:layout_height="170dp">
             <ImageView 
                    android:id="@+id/mars_image"
            ...
</FrameLayout>
  1. Para o ImageView, mude o atributo android:layout_height para match_parent para preencher o novo FrameLayout pai.
android:layout_height="match_parent"
  1. Adicione um segundo elemento <ImageView>, logo abaixo do primeiro, dentro do FrameLayout. Use a definição mostrada abaixo. Essa imagem aparece no canto inferior direito do item da grade, na parte superior da imagem de Marte, e usa o drawable definido em res/drawable/ic_for_sale_outline.xml para o ícone de cifrão.
<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. Adicione o atributo android:visibility à visualização da imagem mars_property_type. Use uma expressão de vinculação para testar o tipo de propriedade e atribuir a visibilidade a View.GONE (para uma locação) ou View.VISIBLE (a uma compra).
 android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"

Até agora, você só viu expressões de vinculação em layouts que usam variáveis individuais definidas no elemento <data>. As expressões de vinculação são muito eficientes e permitem realizar operações como testes e cálculos matemáticos inteiramente no seu layout XML. Nesse caso, use o operador ternário (?:) para realizar um teste (este objeto é uma locação?). Você fornece um resultado para "true" (ocultar o ícone de cifrão com View.GONE) e outro para "false" (mostrar o ícone com View.VISIBLE).

O novo arquivo grid_view_item.xml completo é mostrado abaixo:

<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. Compile e execute o app. Observe que as propriedades que não estão disponíveis para aluguel têm o ícone de cifrão.

Atualmente, seu app exibe todas as propriedades de Marte na grade de visão geral. Se um usuário estivesse comprando uma propriedade para aluguel em Marte, seria útil ter os ícones para indicar quais das propriedades disponíveis à venda seriam úteis, mas ainda há muitas propriedades que precisam ser acessadas na página. Nesta tarefa, você adicionará um menu "opções" ao fragmento de visão geral que permite ao usuário mostrar apenas locações, apenas propriedades para venda ou mostrar todas.

Uma maneira de realizar essa tarefa é testar o tipo de cada MarsProperty na grade de visão geral e exibir apenas as propriedades correspondentes. No entanto, o serviço da Web de Marte tem um parâmetro ou uma opção de consulta (chamado filter) que permite acessar apenas propriedades do tipo rent ou do buy. É possível usar esta consulta de filtro com o URL do serviço da Web realestate em um navegador como este:

https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buy

Nesta tarefa, você modificará a classe MarsApiService para adicionar uma opção de consulta à solicitação de serviço da Web com a Retrofit. Depois, conectar o menu "opções" para fazer o download novamente de todos os dados da propriedade de Marte usando essa opção de consulta. Como a resposta recebida do serviço da Web contém apenas as propriedades em que você tem interesse, não é necessário alterar a lógica de visualização da grade de visão geral.

Etapa 1: atualizar o serviço da API Mars

Para mudar a solicitação, é necessário revisitar a classe MarsApiService implementada no primeiro codelab desta série. Você modifica a classe para fornecer uma API de filtragem.

  1. Abra o network/MarsApiService.kt Logo abaixo das importações, crie um enum chamado MarsApiFilter para definir constantes que correspondam aos valores de consulta esperados pelo serviço da Web.
enum class MarsApiFilter(val value: String) {
   SHOW_RENT("rent"),
   SHOW_BUY("buy"),
   SHOW_ALL("all") }
  1. Modifique o método getProperties() para usar a entrada de string da consulta de filtro e anote essa entrada com @Query("filter"), conforme mostrado abaixo.

    Importe retrofit2.http.Query quando solicitado.

    A anotação @Query instrui o método getProperties() (e, portanto, a Retrofit) a fazer a solicitação de serviço da Web com a opção de filtro. Cada vez que getProperties() é chamado, o URL de solicitação inclui a parte ?filter=type, que direciona o serviço da Web para responder com resultados que correspondam a essa consulta.
fun getProperties(@Query("filter") type: String):  

Etapa 2: atualizar o modelo de visualização geral

Você solicita dados da MarsApiService no método getMarsRealEstateProperties() em OverviewViewModel. Agora você precisa atualizar essa solicitação para usar o argumento de filtro.

  1. Abra o overview/OverviewViewModel.kt Você verá erros no Android Studio devido às mudanças feitas na etapa anterior. Adicione MarsApiFilter (a enumeração de possíveis valores de filtro) como um parâmetro à chamada de getMarsRealEstateProperties().

    Importe com.example.android.marsrealestate.network.MarsApiFilter quando solicitado.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. Modifique a chamada para getProperties() no serviço Retrofit para transmitir essa consulta de filtro como uma string.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
  1. No bloco init {}, transmita MarsApiFilter.SHOW_ALL como um argumento para getMarsRealEstateProperties(), a fim de mostrar todas as propriedades quando o app for carregado pela primeira vez.
init {
   getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
  1. No final da classe, adicione um método updateFilter() que usa um argumento MarsApiFilter e chama getMarsRealEstateProperties() com esse argumento.
fun updateFilter(filter: MarsApiFilter) {
   getMarsRealEstateProperties(filter)
}

Etapa 3: conectar o fragmento ao menu de opções

A última etapa é conectar o menu flutuante ao fragmento para chamar updateFilter() no modelo de visualização quando o usuário escolher uma opção de menu.

  1. Abra o res/menu/overflow_menu.xml O app MarsRealEstate tem um menu flutuante existente que fornece as três opções disponíveis: mostrar todas as propriedades, mostrar apenas aluguéis e mostrar apenas as propriedades à venda.
<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. Abra o overview/OverviewFragment.kt No final da classe, implemente o método onOptionsItemSelected() para processar seleções de itens de menu.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. Em onOptionsItemSelected(), chame o método updateFilter() no modelo de visualização com o filtro apropriado. Use um bloco when {} do Kotlin para alternar entre as opções. Use MarsApiFilter.SHOW_ALL como o valor do filtro padrão. Retorne true, porque você processou o item de menu. Importe MarsApiFilter (com.example.android.marsrealestate.network.MarsApiFilter) quando solicitado. O método onOptionsItemSelected() completo está mostrado abaixo.
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. Compile e execute o app. Ele inicia a primeira grade de visão geral com todos os tipos de propriedade e as propriedades à venda marcadas com o ícone de dólar.
  2. Escolha Alugar no menu de opções. As propriedades são atualizadas, e nenhuma delas é exibida com o ícone de dólar. Somente as propriedades para locação são exibidas. Pode ser necessário aguardar alguns instantes para que a tela seja atualizada e mostre apenas as propriedades filtradas.
  3. Escolha Comprar no menu de opções. As propriedades são atualizadas novamente, e todas aparecem com o ícone de dólar. Somente as propriedades à venda são exibidas.

Agora você tem uma grade de ícones de rolagem para as propriedades de Marte, mas é hora de ver mais detalhes. Nesta tarefa, você adicionará um fragmento de detalhes para exibir informações de uma propriedade específica. O fragmento de detalhes mostra uma imagem maior, o preço e o tipo de propriedade, seja para aluguel ou venda.

Esse fragmento é iniciado quando o usuário toca em uma imagem na grade de visão geral. Para fazer isso, você precisa adicionar um listener onClick aos itens da grade do RecyclerView e navegar até o novo fragmento. Para navegar, acione uma mudança LiveData no ViewModel, como fez ao longo dessas lições. Você também pode usar o plug-in Safe Args do componente de navegação para transmitir as informações selecionadas de MarsProperty do fragmento de visão geral para o fragmento de detalhes.

Etapa 1: criar o modelo de visualização de detalhes e atualizar o layout

Da mesma forma que o processo usado para o modelo de visualização de visão geral e os fragmentos, agora você precisa implementar o modelo de visualização e os arquivos de layout do fragmento de detalhe.

  1. Abra o detail/DetailViewModel.kt Assim como os arquivos Kotlin relacionados à rede estão contidos na pasta network e nos arquivos de visão geral em overview, a pasta detail contém os arquivos associados à visualização detalhada. A classe DetailViewModel (vazia no momento) usa um marsProperty como parâmetro no construtor.
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. Na definição da classe, adicione LiveData para a propriedade de Marte selecionada, exibindo essas informações à visualização de detalhes. Siga o padrão normal de criar um MutableLiveData para armazenar o MarsProperty e, em seguida, expor uma propriedade LiveData pública imutável.

    Importe androidx.lifecycle.LiveData e importe androidx.lifecycle.MutableLiveData quando solicitado.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. Crie um bloco init {} e defina o valor da propriedade de Marte selecionada com o objeto MarsProperty do construtor.
    init {
        _selectedProperty.value = marsProperty
    }
  1. Abra res/layout/fragment_detail.xml e olhe para a visualização de design.

    Este é o arquivo de layout do fragmento de detalhe. Ele contém uma ImageView para a foto grande, uma TextView para o tipo de propriedade (aluguel ou venda) e uma TextView para o preço. O layout de restrição é unido com uma ScrollView para que ela role automaticamente se a visualização ficar muito grande para a tela. Por exemplo, quando o usuário a visualiza no modo paisagem.
  2. Acesse a guia Texto do layout. Na parte superior do layout, antes do elemento <ScrollView>, adicione um elemento <data> para associar o modelo de visualização de detalhes ao layout.
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
  1. Adicione o atributo app:imageUrl ao elemento ImageView. Defina-o como imgSrcUrl na propriedade selecionada do modelo de visualização.

    O adaptador de vinculação que carrega uma imagem usando o Glide também será usado aqui automaticamente, já que ele monitora todos os atributos app:imageUrl.
 app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"

Etapa 2: definir a navegação no modelo de visualização de visão geral

Quando o usuário toca em uma foto no modelo de visão geral, ele aciona a navegação para um fragmento que mostra detalhes sobre o item clicado.

  1. Abra o overview/OverviewViewModel.kt Adicione uma propriedade _navigateToSelectedProperty MutableLiveData e a exponha a um LiveData imutável.

    Quando o LiveData muda para não nulo, a navegação é acionada. Em breve, você adicionará o código para observar essa variável e acionar a navegação.
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
   get() = _navigateToSelectedProperty
  1. No final da classe, adicione um método displayPropertyDetails() que define _navigateToSelectedProperty como a propriedade de Marte selecionada.
fun displayPropertyDetails(marsProperty: MarsProperty) {
   _navigateToSelectedProperty.value = marsProperty
}
  1. Adicione um método displayPropertyDetailsComplete() que anula o valor de _navigateToSelectedProperty. Ela é necessária para marcar o estado de navegação como concluído e evitar que ela seja acionada novamente quando o usuário retornar da visualização de detalhes.
fun displayPropertyDetailsComplete() {
   _navigateToSelectedProperty.value = null
}

Etapa 3: configurar os listeners de clique no adaptador de grade e no fragmento

  1. Abra o overview/PhotoGridAdapter.kt No final da classe, crie uma classe OnClickListener personalizada que use um lambda com um parâmetro marsProperty. Na classe, defina uma função onClick() definida como o parâmetro lambda.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. Role para cima até a definição da classe PhotoGridAdapter e adicione uma propriedade OnClickListener particular ao construtor.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. Para tornar uma foto clicável, adicione onClickListener ao item da grade no método onBindviewHolder(). Defina o listener de clique entre as chamadas para getItem() and bind().
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
   val marsProperty = getItem(position)
   holder.itemView.setOnClickListener {
       onClickListener.onClick(marsProperty)
   }
   holder.bind(marsProperty)
}
  1. Abra o overview/OverviewFragment.kt No método onCreateView(), substitua a linha que inicializa a propriedade binding.photosGrid.adapter pela linha mostrada abaixo.

    Esse código adiciona o objeto PhotoGridAdapter.onClickListener ao construtor da PhotoGridAdapter e chama viewModel.displayPropertyDetails() com o objeto MarsProperty transmitido. Isso aciona a LiveData no modelo de visualização para a navegação.
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
   viewModel.displayPropertyDetails(it)
})

Etapa 4: modificar o gráfico de navegação e tornar MarsProperty parcelable.

Quando um usuário toca em uma foto na grade de visão geral, o app precisa navegar até o fragmento de detalhes e transmitir os detalhes da propriedade de Marte selecionada para que a visualização de detalhes possa exibir essas informações.

No momento, você tem um listener de clique do PhotoGridAdapter para gerenciar o toque e uma maneira de acionar a navegação a partir do modelo de visualização. Mas você ainda não tem um objeto MarsProperty transmitido para o fragmento de detalhes. Para isso, use o Safe Args do componente de navegação.

  1. Abra o res/navigation/nav_graph.xml Clique na guia Text para ver o código XML do gráfico de navegação.
  2. No elemento <fragment> do fragmento de detalhe, adicione o elemento <argument> mostrado abaixo. Esse argumento, chamado selectedProperty, tem o tipo MarsProperty.
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. Compile o aplicativo. A navegação gera um erro porque o MarsProperty não é parcelable. A interface Parcelable permite que os objetos sejam serializados, para que os dados dos objetos possam ser transmitidos entre fragmentos ou atividades. Nesse caso, para que os dados no objeto MarsProperty sejam transmitidos para o fragmento de detalhes pelo Safe Args, o MarsProperty precisa implementar a interface Parcelable. A boa notícia é que o Kotlin oferece um atalho fácil para implementar essa interface.
  2. Abra o network/MarsProperty.kt Adicione a anotação @Parcelize à definição da classe.

    Importe kotlinx.android.parcel.Parcelize quando solicitado.

    A anotação @Parcelize usa as extensões Android do Kotlin para implementar automaticamente os métodos na interface Parcelable dessa classe. Você não precisa fazer mais nada!
@Parcelize
data class MarsProperty (
  1. Mude a definição da classe MarsProperty para estender Parcelable.

    Importe android.os.Parcelable quando solicitado.

    A definição da classe MarsProperty agora ficará assim:
@Parcelize
data class MarsProperty (
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double) : Parcelable {

Etapa 5: conectar os fragmentos

Você ainda não está navegando: a navegação real acontece nos fragmentos. Nesta etapa, você adicionará os últimos bits para implementar a navegação entre a visão geral e os fragmentos de detalhes.

  1. Abra o overview/OverviewFragment.kt Em onCreateView(), abaixo das linhas que inicializam o adaptador de grade de fotos, adicione as linhas mostradas abaixo para observar o navigatedToSelectedProperty do modelo de visualização de visão geral.

    Importe androidx.lifecycle.Observer e importe androidx.navigation.fragment.findNavController quando solicitado.

    O observador testa se MarsProperty, o it no lambda, não é nulo. Em caso afirmativo, ele recebe o controlador de navegação do fragmento com findNavController(). Chame displayPropertyDetailsComplete() para instruir o modelo de visualização a redefinir o LiveData para o estado nulo. Assim, você não acionará por engano a navegação novamente quando o app retornar para o OverviewFragment.
viewModel.navigateToSelectedProperty.observe(this, Observer {
   if ( null != it ) {   
      this.findNavController().navigate(
              OverviewFragmentDirections.actionShowDetail(it))             
      viewModel.displayPropertyDetailsComplete()
   }
})
  1. Abra o detail/DetailFragment.kt Adicione esta linha logo abaixo da chamada para setLifecycleOwner() no método onCreateView(). Essa linha recebe o objeto MarsProperty selecionado do Safe Args.

    Observe o uso do operador de declaração não nulo do Kotlin (!!). Se o selectedProperty não estiver lá, algo terrível aconteceu e você quer que o código gere um ponteiro nulo. No código de produção, esse erro precisa ser processado de alguma maneira.
 val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
  1. Adicione esta linha para receber um novo DetailViewModelFactory. Você usará o DetailViewModelFactory para acessar uma instância do DetailViewModel. O app inicial inclui uma implementação de DetailViewModelFactory, então você só precisa inicializá-lo.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. Por fim, adicione esta linha para receber um DetailViewModel da fábrica e conectar todas as partes.
      binding.viewModel = ViewModelProviders.of(
                this, viewModelFactory).get(DetailViewModel::class.java)
  1. Compile e execute o app. Depois, toque em qualquer foto de propriedade de Marte. O fragmento de detalhes é exibido para os detalhes dessa propriedade. Toque no botão "Voltar" para retornar à página de visão geral e perceber que a tela de detalhes ainda está esparsa. Você concluirá a adição dos dados da propriedade a essa página de detalhes na próxima tarefa.

No momento, a página de detalhes mostra apenas a mesma foto de Marte que você está acostumado a ver na página de visão geral. A classe MarsProperty também tem um tipo de propriedade (aluguel ou compra) e um preço de propriedade. A tela de detalhes deve incluir ambos os valores, e seria útil se as propriedades de aluguel indicassem que o preço era um valor por mês. As transformações LiveData são usadas no modelo de visualização para implementar as duas coisas.

  1. Abra o res/values/strings.xml O código inicial inclui recursos de string, mostrados abaixo, para ajudar você a criar as strings para a visualização de detalhes. Para o preço, você usará o recurso display_price_monthly_rental ou o recurso display_price, dependendo do tipo de propriedade.
<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. Abra o detail/DetailViewModel.kt Na parte inferior da turma, adicione o código abaixo.

    Importe androidx.lifecycle.Transformations se solicitado.

    Essa transformação testa se a propriedade selecionada é uma locação usando o mesmo teste da primeira tarefa. Se a propriedade for uma locação, a transformação escolherá a string apropriada dos recursos com um switch when {} de Kotlin. Essas duas strings precisam de um número no final. Por isso, você concatena a property.price posteriormente.
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. Importe a classe R gerada para ter acesso aos recursos da string no projeto.
import com.example.android.marsrealestate.R
  1. Após a transformação do displayPropertyPrice, adicione o código mostrado abaixo. Essa transformação concatena vários recursos de string, dependendo se o tipo de propriedade é uma locação.
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. Abra o res/layout/fragment_detail.xml Só há mais uma coisa a fazer, que é vincular as novas strings (criadas com as transformações LiveData) à visualização de detalhes. Para fazer isso, defina o valor do campo de texto do texto do tipo de propriedade como viewModel.displayPropertyType e o campo de texto do texto do valor de preços como 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. Compile e execute o app. Agora, todos os dados da propriedade serão exibidos na página de detalhes e estarão bem formatados.

Projeto do Android Studio: MarsRealEstateFinal

Expressões de vinculação

  • Use expressões de vinculação em arquivos de layout XML para executar operações programáticas simples, como testes matemáticos ou condicionais, em dados vinculados.
  • Para fazer referência a classes dentro do arquivo de layout, use a tag <import> dentro da tag <data>.

Opções de consulta do serviço da Web

  • As solicitações para serviços da Web podem incluir parâmetros opcionais.
  • Para especificar parâmetros de consulta na solicitação, use a anotação @Query na Retrofit.

Curso da Udacity:

Documentação do desenvolvedor Android:

Outro:

Esta seção lista as possíveis atividades para os alunos que estão trabalhando neste codelab como parte de um curso ministrado por um instrutor. Cabe ao instrutor fazer o seguinte:

  • Se necessário, atribua o dever de casa.
  • Informe aos alunos como enviar o dever de casa.
  • Atribua nota aos trabalhos de casa.

Os professores podem usar essas sugestões o quanto quiserem, e eles devem se sentir à vontade para passar o dever de casa como achar adequado.

Se você estiver fazendo este codelab por conta própria, use essas atividades para testar seu conhecimento.

Responda a estas perguntas:

Pergunta 1

O que a tag <import> faz em um arquivo de layout XML?

▢ Incluir um arquivo de layout em outro.

▢ Incorporar código Kotlin no arquivo de layout.

▢ Fornecer acesso a propriedades vinculadas a dados.

▢ Permitir que você referencie classes e membros de classes em expressões de vinculação.

Pergunta 2

Como adicionar uma opção de consulta a uma chamada de serviço da Web REST na Retrofit?

▢ Anexe a consulta ao final do URL de solicitação.

▢ Adicione um parâmetro para a consulta à função que faz a solicitação e anote esse parâmetro com @Query.

▢ Use a classe Query para criar uma solicitação.

▢ Use o método addQuery() na builder da Retrofit.

Inicie a próxima lição: 9.1: Repositório

Para ver links de outros codelabs neste curso, consulte a página de destino dos codelabs do curso Conceitos básicos do Kotlin para Android.