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.
- Abra o app MarsRealEstate do último codelab. Se você não tiver o app MarsRealEstateGrid, poderá fazer o download dele.
- Abra o
network/MarsProperty.kt
Adicione um corpo à definição da classeMarsProperty
e adicione um getter personalizado paraisRental
que retornetrue
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.
- Abra o
res/layout/grid_view_item.xml
Esse é o arquivo de layout de cada célula no layout de grade para aRecyclerView
. No momento, o arquivo contém apenas o elemento<ImageView>
da imagem da propriedade. - No elemento
<data>
, adicione um elemento<import>
para a classeView
. 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 constantesView.GONE
eView.VISIBLE
. Portanto, precisa acessar a classeView
.
<import type="android.view.View"/>
- 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>
- Para o
ImageView
, mude o atributoandroid:layout_height
paramatch_parent
para preencher o novoFrameLayout
pai.
android:layout_height="match_parent"
- Adicione um segundo elemento
<ImageView>
, logo abaixo do primeiro, dentro doFrameLayout
. 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 emres/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"/>
- Adicione o atributo
android:visibility
à visualização da imagemmars_property_type
. Use uma expressão de vinculação para testar o tipo de propriedade e atribuir a visibilidade aView.GONE
(para uma locação) ouView.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>
- 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.
- Abra o
network/MarsApiService.kt
Logo abaixo das importações, crie umenum
chamadoMarsApiFilter
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") }
- 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.
Importeretrofit2.http.Query
quando solicitado.
A anotação@Query
instrui o métodogetProperties()
(e, portanto, a Retrofit) a fazer a solicitação de serviço da Web com a opção de filtro. Cada vez quegetProperties()
é 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.
- Abra o
overview/OverviewViewModel.kt
Você verá erros no Android Studio devido às mudanças feitas na etapa anterior. AdicioneMarsApiFilter
(a enumeração de possíveis valores de filtro) como um parâmetro à chamada degetMarsRealEstateProperties()
.
Importecom.example.android.marsrealestate.network.MarsApiFilter
quando solicitado.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
- 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)
- No bloco
init {}
, transmitaMarsApiFilter.SHOW_ALL
como um argumento paragetMarsRealEstateProperties()
, a fim de mostrar todas as propriedades quando o app for carregado pela primeira vez.
init {
getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
- No final da classe, adicione um método
updateFilter()
que usa um argumentoMarsApiFilter
e chamagetMarsRealEstateProperties()
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.
- 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>
- Abra o
overview/OverviewFragment.kt
No final da classe, implemente o métodoonOptionsItemSelected()
para processar seleções de itens de menu.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
}
- Em
onOptionsItemSelected()
, chame o métodoupdateFilter()
no modelo de visualização com o filtro apropriado. Use um blocowhen {}
do Kotlin para alternar entre as opções. UseMarsApiFilter.SHOW_ALL
como o valor do filtro padrão. Retornetrue
, porque você processou o item de menu. ImporteMarsApiFilter
(com.example.android.marsrealestate.network.MarsApiFilter
) quando solicitado. O métodoonOptionsItemSelected()
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
}
- 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.
- 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.
- 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.
- Abra o
detail/DetailViewModel.kt
Assim como os arquivos Kotlin relacionados à rede estão contidos na pastanetwork
e nos arquivos de visão geral emoverview
, a pastadetail
contém os arquivos associados à visualização detalhada. A classeDetailViewModel
(vazia no momento) usa ummarsProperty
como parâmetro no construtor.
class DetailViewModel( marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
}
- 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 umMutableLiveData
para armazenar oMarsProperty
e, em seguida, expor uma propriedadeLiveData
pública imutável.
Importeandroidx.lifecycle.LiveData
e importeandroidx.lifecycle.MutableLiveData
quando solicitado.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
get() = _selectedProperty
- Crie um bloco
init {}
e defina o valor da propriedade de Marte selecionada com o objetoMarsProperty
do construtor.
init {
_selectedProperty.value = marsProperty
}
- 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 umaImageView
para a foto grande, umaTextView
para o tipo de propriedade (aluguel ou venda) e umaTextView
para o preço. O layout de restrição é unido com umaScrollView
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. - 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>
- Adicione o atributo
app:imageUrl
ao elementoImageView
. Defina-o comoimgSrcUrl
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 atributosapp: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.
- Abra o
overview/OverviewViewModel.kt
Adicione uma propriedade_navigateToSelectedProperty
MutableLiveData
e a exponha a umLiveData
imutável.
Quando oLiveData
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
- 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
}
- 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
- Abra o
overview/PhotoGridAdapter.kt
No final da classe, crie uma classeOnClickListener
personalizada que use um lambda com um parâmetromarsProperty
. Na classe, defina uma funçãoonClick()
definida como o parâmetro lambda.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
- Role para cima até a definição da classe
PhotoGridAdapter
e adicione uma propriedadeOnClickListener
particular ao construtor.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
- Para tornar uma foto clicável, adicione
onClickListener
ao item da grade no métodoonBindviewHolder()
. Defina o listener de clique entre as chamadas paragetItem() and bind()
.
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
val marsProperty = getItem(position)
holder.itemView.setOnClickListener {
onClickListener.onClick(marsProperty)
}
holder.bind(marsProperty)
}
- Abra o
overview/OverviewFragment.kt
No métodoonCreateView()
, substitua a linha que inicializa a propriedadebinding.photosGrid.adapter
pela linha mostrada abaixo.
Esse código adiciona o objetoPhotoGridAdapter.onClickListener
ao construtor daPhotoGridAdapter
e chamaviewModel.displayPropertyDetails()
com o objetoMarsProperty
transmitido. Isso aciona aLiveData
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.
- Abra o
res/navigation/nav_graph.xml
Clique na guia Text para ver o código XML do gráfico de navegação. - No elemento
<fragment>
do fragmento de detalhe, adicione o elemento<argument>
mostrado abaixo. Esse argumento, chamadoselectedProperty
, tem o tipoMarsProperty
.
<argument
android:name="selectedProperty"
app:argType="com.example.android.marsrealestate.network.MarsProperty"
/>
- Compile o aplicativo. A navegação gera um erro porque o
MarsProperty
não é parcelable. A interfaceParcelable
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 objetoMarsProperty
sejam transmitidos para o fragmento de detalhes pelo Safe Args, oMarsProperty
precisa implementar a interfaceParcelable
. A boa notícia é que o Kotlin oferece um atalho fácil para implementar essa interface. - Abra o
network/MarsProperty.kt
Adicione a anotação@Parcelize
à definição da classe.
Importekotlinx.android.parcel.Parcelize
quando solicitado.
A anotação@Parcelize
usa as extensões Android do Kotlin para implementar automaticamente os métodos na interfaceParcelable
dessa classe. Você não precisa fazer mais nada!
@Parcelize
data class MarsProperty (
- Mude a definição da classe
MarsProperty
para estenderParcelable
.
Importeandroid.os.Parcelable
quando solicitado.
A definição da classeMarsProperty
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.
- Abra o
overview/OverviewFragment.kt
EmonCreateView()
, abaixo das linhas que inicializam o adaptador de grade de fotos, adicione as linhas mostradas abaixo para observar onavigatedToSelectedProperty
do modelo de visualização de visão geral.
Importeandroidx.lifecycle.Observer
e importeandroidx.navigation.fragment.findNavController
quando solicitado.
O observador testa seMarsProperty
, oit
no lambda, não é nulo. Em caso afirmativo, ele recebe o controlador de navegação do fragmento comfindNavController()
. ChamedisplayPropertyDetailsComplete()
para instruir o modelo de visualização a redefinir oLiveData
para o estado nulo. Assim, você não acionará por engano a navegação novamente quando o app retornar para oOverviewFragment
.
viewModel.navigateToSelectedProperty.observe(this, Observer {
if ( null != it ) {
this.findNavController().navigate(
OverviewFragmentDirections.actionShowDetail(it))
viewModel.displayPropertyDetailsComplete()
}
})
- Abra o
detail/DetailFragment.kt
Adicione esta linha logo abaixo da chamada parasetLifecycleOwner()
no métodoonCreateView()
. Essa linha recebe o objetoMarsProperty
selecionado do Safe Args.
Observe o uso do operador de declaração não nulo do Kotlin (!!
). Se oselectedProperty
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
- Adicione esta linha para receber um novo
DetailViewModelFactory
. Você usará oDetailViewModelFactory
para acessar uma instância doDetailViewModel
. O app inicial inclui uma implementação deDetailViewModelFactory
, então você só precisa inicializá-lo.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
- 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)
- 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.
- 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 recursodisplay_price_monthly_rental
ou o recursodisplay_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>
- Abra o
detail/DetailViewModel.kt
Na parte inferior da turma, adicione o código abaixo.
Importeandroidx.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 switchwhen {}
de Kotlin. Essas duas strings precisam de um número no final. Por isso, você concatena aproperty.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)
}
- Importe a classe
R
gerada para ter acesso aos recursos da string no projeto.
import com.example.android.marsrealestate.R
- 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
}))
}
- Abra o
res/layout/fragment_detail.xml
Só há mais uma coisa a fazer, que é vincular as novas strings (criadas com as transformaçõesLiveData
) à visualização de detalhes. Para fazer isso, defina o valor do campo de texto do texto do tipo de propriedade comoviewModel.displayPropertyType
e o campo de texto do texto do valor de preços comoviewModel.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" />
- 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:
- Como desenvolver apps Android com Kotlin (link em inglês)
Documentação do desenvolvedor Android:
- Visão geral do ViewModel
- Visão geral do LiveData
- Como vincular adaptadores
- Layouts e expressões de vinculação
- Navegação
- Primeiros passos com o componente de navegação
- Transmitir dados entre destinos (também descreve o Safe Args)
- Classe
Transformations
- Classe
ViewModelProvider
- Classe
ViewModelProvider.Factory
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:
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.