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ê vai aproveitar mais este curso se fizer os codelabs em sequência. Todos os codelabs do curso estão listados na página inicial dos codelabs de princípios básicos do Kotlin para Android.

Introdução

Nos codelabs anteriores desta lição, você aprendeu a extrair dados sobre imóveis em Marte de um serviço da Web e a criar um RecyclerView com um layout de grade para carregar e mostrar imagens desses dados. Neste codelab, você vai concluir o app MarsRealEstate implementando a capacidade de filtrar os terrenos em Marte de acordo com a disponibilidade para aluguel ou compra. Você também cria uma visualização detalhada para que, se o usuário tocar em uma foto de propriedade na visão geral, ele veja uma visualização detalhada com informações sobre essa 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 eles.
  • Como usar componentes de arquitetura, incluindo ViewModels, fábricas de ViewModel, transformações e LiveData.
  • Como extrair dados codificados em JSON de um serviço REST da Web e analisar esses dados em objetos Kotlin com as bibliotecas Retrofit e Moshi (links em inglês).

O que você vai aprender

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

O que você aprenderá

  • Modifique o app MarsRealEstate para marcar os terrenos em Marte que estão à venda (em vez dos que estão 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 os terrenos em Marte por tipo.
  • Crie um fragmento de detalhes para uma propriedade de Marte, conecte esse fragmento à grade de visão geral com navegação e transmita os dados da propriedade para esse fragmento.

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

Nesta versão do app, você vai trabalhar com o tipo da propriedade (aluguel ou compra) e adicionar um ícone ao layout de grade para marcar as propriedades à venda:

Modifique o menu de opções do app para filtrar a grade e mostrar apenas as propriedades para aluguel ou venda:

Por fim, você vai criar uma visualização detalhada para um terreno e conectar os ícones na grade de visão geral a esse fragmento de detalhes com navegação:

Até agora, a única parte dos dados da propriedade em Marte que você usou foi o URL da imagem da propriedade. Mas os dados da propriedade, que você definiu na classe MarsProperty, também incluem um ID, um preço e um tipo (aluguel ou venda). Para relembrar, aqui está um snippet dos dados JSON que você recebe 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ê vai 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 MarsProperty para incluir o tipo

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

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

  1. Abra o app MarsRealEstate do último codelab. (Faça o download do MarsRealEstateGrid se você não tiver o app.)
  2. Abra network/MarsProperty.kt. Adicione um corpo à definição da classe MarsProperty e 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 para a grade de imagens e mostre um ícone de cifrão apenas nas imagens de propriedades à venda:

Com as expressões de vinculação de dados, é possível fazer esse teste totalmente no layout XML dos itens da grade.

  1. Abra res/layout/grid_view_item.xml. Esse é o arquivo de layout de cada célula individual no layout de grade do RecyclerView. No momento, o arquivo contém apenas o elemento <ImageView> para a imagem da propriedade.
  2. Dentro do elemento <data>, adicione um elemento <import> para a classe View. Você usa importações quando quer usar componentes de uma classe em uma expressão de vinculação de dados em um arquivo de layout. Nesse caso, você vai usar as constantes View.GONE e View.VISIBLE. Portanto, é necessário ter acesso à classe View.
<import type="android.view.View"/>
  1. Envolva toda a visualização de imagem com um FrameLayout para permitir que o elemento drawable de cifrão seja empilhado em cima da 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 e preencha o novo pai FrameLayout.
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, em cima da imagem de Marte, e usa o elemento 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 de imagem mars_property_type. Use uma expressão de vinculação para testar o tipo de propriedade e atribua a visibilidade a View.GONE (para uma locação) ou View.VISIBLE (para 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 extremamente poderosas e permitem realizar operações como testes e cálculos matemáticos totalmente no layout XML. Nesse caso, você usa o operador ternário (?:) para realizar um teste (este objeto é uma locação?). Você fornece um resultado para verdadeiro (ocultar o ícone de cifrão com View.GONE) e outro para falso (mostrar esse í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 são aluguéis têm o ícone de cifrão.

No momento, o app mostra todos os terrenos em Marte na grade de visão geral. Se um usuário estivesse procurando um imóvel para alugar em Marte, seria útil ter os ícones para indicar quais das propriedades disponíveis estão à venda, mas ainda há muitas propriedades para rolar na página. Nesta tarefa, você vai adicionar um menu de opções ao fragmento de visão geral que permite ao usuário mostrar apenas imóveis para aluguel, apenas para venda ou todos.

Uma maneira de fazer isso é testar o tipo de cada MarsProperty na grade de visão geral e mostrar apenas as propriedades correspondentes. No entanto, o serviço da Web de Marte tem um parâmetro ou opção de consulta (chamado filter) que permite receber apenas propriedades do tipo rent ou buy. Você pode usar essa consulta de filtro com o URL do serviço da Web realestate em um navegador assim:

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

Nesta tarefa, você vai modificar a classe MarsApiService para adicionar uma opção de consulta à solicitação de serviço da Web com a Retrofit. Em seguida, conecte o menu de opções para baixar novamente todos os dados de terrenos em Marte usando essa opção de consulta. Como a resposta que você recebe do serviço da Web contém apenas as propriedades de seu interesse, não é necessário mudar a lógica de exibição da visualização para a grade de visão geral.

Etapa 1: atualizar o serviço da API Mars

Para mudar a solicitação, você precisa acessar novamente a classe MarsApiService que implementou no primeiro codelab desta série. Você modifica a classe para fornecer uma API de filtragem.

  1. Abra 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 receber uma entrada de string para a consulta de filtro e anote essa entrada com @Query("filter"), conforme mostrado abaixo.

    Importe retrofit2.http.Query quando solicitado.

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

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

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

  1. Abra overview/OverviewViewModel.kt. Você vai ver erros no Android Studio devido às mudanças feitas na etapa anterior. Adicione MarsApiFilter (o enum de possíveis valores de filtro) como um parâmetro à chamada 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() e mostre 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 de estouro ao fragmento para chamar updateFilter() no modelo de visualização quando o usuário escolher uma opção de menu.

  1. Abra res/menu/overflow_menu.xml. O app MarsRealEstate tem um menu de estouro que oferece três opções: mostrar todas as propriedades, mostrar apenas aluguéis e mostrar apenas 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 overview/OverviewFragment.kt. No final da classe, implemente o método onOptionsItemSelected() para processar as seleções de itens do menu.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. Em onOptionsItemSelected(), chame o método updateFilter() no modelo de visualização com o filtro adequado. Use um bloco when {} do Kotlin para alternar entre as opções. Use MarsApiFilter.SHOW_ALL para o valor de 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 é 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 vai abrir a primeira grade de visão geral com todos os tipos de propriedades e as propriedades à venda marcadas com o ícone de dólar.
  2. Escolha Alugar no menu de opções. As propriedades são recarregadas, e nenhuma delas aparece com o ícone de dólar. (Somente imóveis para aluguel são mostrados.) Talvez seja 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 recarregadas novamente, e todas aparecem com o ícone de dólar. (Somente propriedades à venda são mostradas.)

Agora você tem uma grade rolável de ícones para propriedades de Marte, mas é hora de ter mais detalhes. Nesta tarefa, você vai adicionar um fragmento de detalhes para mostrar os detalhes de uma propriedade específica. O fragmento de detalhes vai mostrar 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, adicione um listener onClick aos itens da grade RecyclerView e navegue até o novo fragmento. Você navega acionando uma mudança de LiveData no ViewModel, como fez ao longo destas lições. Você também usa o plug-in Safe Args do componente Navigation para transmitir as informações MarsProperty selecionadas 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 de detalhes

Semelhante ao processo usado para o modelo de visualização e os fragmentos de visão geral, agora você precisa implementar o modelo de visualização e os arquivos de layout para o fragmento de detalhes.

  1. Abra detail/DetailViewModel.kt. Assim como os arquivos Kotlin relacionados à rede estão na pasta network e os arquivos de visão geral em overview, a pasta detail contém os arquivos associados à visualização de detalhes. 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 e exponha essas informações na visualização de detalhes. Siga o padrão usual de criar um MutableLiveData para manter o próprio MarsProperty e, em seguida, exponha uma propriedade LiveData pública imutável.

    Importe androidx.lifecycle.LiveData e 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 confira na visualização de design.

    Este é o arquivo de layout do fragmento de detalhes. Ele contém um ImageView para a foto grande, um TextView para o tipo de propriedade (aluguel ou venda) e um TextView para o preço. O layout de restrição é envolvido por um ScrollView para rolar 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 de cima do layout, logo 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 como o imgSrcUrl da propriedade selecionada do modelo de visualização.

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

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

Quando o usuário toca em uma foto no modelo de visão geral, isso deve acionar a navegação até um fragmento que mostra detalhes sobre o item clicado.

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

    Quando esse LiveData muda para não nulo, a navegação é acionada. Em breve, você vai 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 defina como nulo o valor de _navigateToSelectedProperty. Isso é necessário para marcar o estado de navegação como concluído e evitar que a navegação seja acionada novamente quando o usuário voltar 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 overview/PhotoGridAdapter.kt. No final da classe, crie uma classe OnClickListener personalizada que usa uma lambda com um parâmetro marsProperty. Dentro da 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 o 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 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 PhotoGridAdapter e chama viewModel.displayPropertyDetails() com o objeto MarsProperty transmitido. Isso aciona o 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 selecionada de Marte para que a visualização detalhada possa mostrar essas informações.

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

  1. Abra res/navigation/nav_graph.xml. Clique na guia Text para conferir o código XML do gráfico de navegação.
  2. No elemento <fragment> do fragmento de detalhes, 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 app. A navegação vai mostrar um erro porque o MarsProperty não é parcelable. A interface Parcelable permite que os objetos sejam serializados para que os dados deles possam ser transmitidos entre fragmentos ou atividades. Nesse caso, para que os dados dentro do objeto MarsProperty sejam transmitidos ao fragmento de detalhes via Safe Args, MarsProperty precisa implementar a interface Parcelable. A boa notícia é que o Kotlin oferece um atalho fácil para implementar essa interface.
  2. Abra 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 do Kotlin para Android para implementar automaticamente os métodos na interface Parcelable para essa classe. Não é necessário 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 é 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ê vai adicionar os últimos bits para implementar a navegação entre os fragmentos de visão geral e detalhes.

  1. Abra 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 androidx.navigation.fragment.findNavController quando solicitado.

    O observador testa se MarsProperty, o it na lambda, não é nulo. Se for, ele recebe o controlador de navegação do fragmento com findNavController(). Chame displayPropertyDetailsComplete() para informar à ViewModel que precisa redefinir o LiveData para o estado nulo. Assim, você não vai acionar a navegação por engano quando o app voltar para o OverviewFragment.
viewModel.navigateToSelectedProperty.observe(this, Observer {
   if ( null != it ) {   
      this.findNavController().navigate(
              OverviewFragmentDirections.actionShowDetail(it))             
      viewModel.displayPropertyDetailsComplete()
   }
})
  1. Abra detail/DetailFragment.kt. Adicione esta linha logo abaixo da chamada para setLifecycleOwner() no método onCreateView(). Essa linha recebe o objeto MarsProperty selecionado dos Safe Args.

    Observe o uso do operador de asserção não nula 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, você precisa processar esse erro de alguma forma.
 val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
  1. Adicione esta linha em seguida para receber um novo DetailViewModelFactory. Você vai usar o DetailViewModelFactory para receber uma instância do DetailViewModel. O app inicial inclui uma implementação de DetailViewModelFactory. Portanto, basta inicializar.
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 aparece para os detalhes dessa propriedade. Toque no botão "Voltar" para retornar à página de visão geral. Observe que a tela de detalhes ainda está um pouco vazia. Você vai terminar de adicionar os dados da propriedade à 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. A tela de detalhes precisa incluir os dois valores, e seria útil se os imóveis para aluguel indicassem que o preço é mensal. Você usa transformações LiveData no modelo de visualização para implementar as duas coisas.

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

    Importe androidx.lifecycle.Transformations, se solicitado.

    Essa transformação testa se a propriedade selecionada é um aluguel, usando o mesmo teste da primeira tarefa. Se a propriedade for um aluguel, a transformação vai escolher a string adequada nos recursos com uma chave when {} do Kotlin. As duas strings precisam de um número no final. Portanto, você concatena o property.price depois.
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 acessar os recursos de string no projeto.
import com.example.android.marsrealestate.R
  1. Depois da transformação displayPropertyPrice, adicione o código mostrado abaixo. Essa transformação concatena vários recursos de string, com base no tipo de propriedade, se é um aluguel.
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 res/layout/fragment_detail.xml. Só falta mais uma coisa: vincular as novas strings (criadas com as transformações LiveData) à visualização de detalhes. Para isso, defina o valor do campo de texto para o texto do tipo de propriedade como viewModel.displayPropertyType e o campo de texto para o texto do valor do preço 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 de propriedade aparecem na página de detalhes, bem formatados.

Projeto do Android Studio: MarsRealEstateFinal (link em inglês)

Expressões de vinculação

  • Use expressões de vinculação em arquivos de layout XML para realizar operações programáticas simples, como matemática ou testes condicionais, em dados vinculados.
  • Para referenciar 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 no Retrofit.

Curso da Udacity:

Documentação do desenvolvedor Android:

Outro:

Esta seção lista as possíveis atividades de dever de casa para os alunos que estão fazendo este codelab como parte de um curso ministrado por um professor. Cabe ao professor fazer o seguinte:

  • Atribuir o dever de casa, se necessário.
  • Informar aos alunos como enviar deveres de casa.
  • Atribuir nota aos deveres de casa.

Os professores podem usar essas sugestões o quanto quiserem, podendo passar os exercícios que acharem mais apropriados como dever de casa.

Se você estiver seguindo este codelab por conta própria, sinta-se à vontade para usar esses deveres de casa para testar seu conhecimento.

Responda 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 do Kotlin ao arquivo de layout.

▢ Fornecer acesso a propriedades vinculadas a dados.

▢ Permitir referências a 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() no builder da Retrofit.

Comece a próxima lição: 9.1: repositório

Para acessar links de outros codelabs neste curso, consulte a página inicial dos codelabs de conceitos básicos do Kotlin para Android.