Conceitos básicos do Kotlin para Android 08.2: como carregar e mostrar imagens 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

No codelab anterior, você aprendeu a receber dados de um serviço da Web e analisar a resposta em um objeto de dados. Neste codelab, você vai aproveitar esse conhecimento para carregar e mostrar fotos de um URL da Web. Também vamos lembrar como criar uma RecyclerView e usá-la para mostrar uma grade de imagens na página de visão geral.

O que você já precisa saber

  • Como criar e usar fragmentos.
  • Como usar componentes de arquitetura, incluindo ViewModels, fábricas de ViewModel, transformações e LiveData.
  • Saber como extrair um arquivo JSON de um serviço REST da Web e analisar esses dados em objetos Kotlin usando as bibliotecas Retrofit e Moshi (links em inglês).
  • Como criar um layout de grade usando um RecyclerView.
  • Como Adapter, ViewHolder e DiffUtil funcionam.

O que você vai aprender

  • Como usar a biblioteca Glide para carregar e mostrar uma imagem de um URL da Web.
  • Como usar um RecyclerView e um adaptador de grade para exibir uma grade de imagens.
  • Como processar erros possíveis durante o download e a exibição das imagens.

O que você aprenderá

  • Modificar o app MarsRealEstate para acessar o URL dos dados de propriedades de Marte e usar o Glide para carregar e mostrar essas imagens.
  • Adicionar uma animação e um ícone de erro de carregamento ao app.
  • Usará um RecyclerView para mostrar uma grade de imagens de propriedades de Marte.
  • Adicionará o status e o processamento de erros ao RecyclerView.

Neste codelab (e nos relacionados), você vai trabalhar com um app chamado MarsRealEstate, que mostra propriedades à venda em Marte. O 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.

A versão do app que você vai criar neste codelab preenche a página de visão geral, que mostra uma grade de imagens. As imagens fazem parte dos dados de propriedade que o app recebe do serviço da Web de imóveis em Marte. Seu app vai usar a biblioteca Glide para carregar e mostrar as imagens, e um RecyclerView para criar o layout de grade para elas. O app também processará os erros de rede corretamente.

Mostrar uma foto de um URL da Web pode parecer simples, mas requer muito trabalho técnico para que isso funcione bem. A imagem precisa ser transferida por download, armazenada em buffer e decodificada do formato compactado para uma imagem que o Android possa usar. A imagem precisa ser armazenada em cache na memória, no cache baseado em armazenamento ou em ambos. Tudo isso precisa acontecer em segmentos de baixa prioridade em segundo plano para que a IU permaneça responsiva. Além disso, para melhor o desempenho de rede e CPU, convém buscar e decodificar mais de uma imagem ao mesmo tempo. Aprender a carregar imagens da rede de forma eficaz pode ser um codelab por si só.

Felizmente, é possível usar uma biblioteca criada pela comunidade, a Glide (link em inglês), para fazer o download, armazenar em um buffer, decodificar e armazenar as imagens em cache. O Glide deixa você com muito menos trabalho do que se tivesse que fazer tudo isso do zero.

Em resumo, a Glide precisa de duas coisas:

  • O URL da imagem que você quer carregar e mostrar.
  • Um objeto ImageView para mostrar essa imagem.

Nesta tarefa, você vai aprender a usar o Glide para mostrar uma única imagem do web service imobiliário. Você vai mostrar a imagem que representa o primeiro terreno em Marte na lista de propriedades que o serviço da Web retorna. Veja as capturas de tela antes e depois:

Etapa 1: adicionar a dependência do Glide

  1. Abra o app MarsRealEstate do último codelab. (Faça o download do MarsRealEstateNetwork aqui se você não tiver o app.)
  2. Execute o app para ver o que ele faz. (Ele mostra detalhes de texto de uma propriedade que está hipoteticamente disponível em Marte.)
  3. Abra o arquivo build.gradle (Module: app).
  4. Na seção dependencies, adicione esta linha para a biblioteca Glide:
implementation "com.github.bumptech.glide:glide:$version_glide"


O número da versão já está definido separadamente no arquivo Gradle do projeto.

  1. Clique em Sync Now para recriar o projeto com a nova dependência.

Etapa 2: atualizar o modelo de visualização

Em seguida, atualize a classe OverviewViewModel para incluir dados em tempo real de uma única propriedade de Marte.

  1. Abra overview/OverviewViewModel.kt. Logo abaixo do LiveData para o _response, adicione dados dinâmicos internos (mutáveis) e externos (imutáveis) para um único objeto MarsProperty.

    Importe a classe MarsProperty (com.example.android.marsrealestate.network.MarsProperty) quando solicitado.
private val _property = MutableLiveData<MarsProperty>()

val property: LiveData<MarsProperty>
   get() = _property
  1. No método getMarsRealEstateProperties(), encontre a linha no bloco try/catch {} que define _response.value como o número de propriedades. Adicione o teste mostrado abaixo. Se os objetos MarsProperty estiverem disponíveis, esse teste vai definir o valor de _property LiveData como a primeira propriedade em listResult.
if (listResult.size > 0) {   
    _property.value = listResult[0]
}

O bloco try/catch {} completo agora ficará assim:

try {
   var listResult = getPropertiesDeferred.await()
   _response.value = "Success: ${listResult.size} Mars properties retrieved"
   if (listResult.size > 0) {      
       _property.value = listResult[0]
   }
 } catch (e: Exception) {
    _response.value = "Failure: ${e.message}"
 }
  1. Abra o arquivo res/layout/fragment_overview.xml. No elemento <TextView>, mude android:text para vincular ao componente imgSrcUrl do property LiveData:
android:text="@{viewModel.property.imgSrcUrl}"
  1. Execute o app. O TextView mostra apenas o URL da imagem na primeira propriedade de Marte. Você já configurou o ViewModel e o LiveData para o URL.

Etapa 3: criar um adaptador de vinculação e chamar o Glide

Agora você tem o URL de uma imagem para mostrar, e é hora de começar a trabalhar com o Glide para carregar essa imagem. Nesta etapa, você vai usar um adaptador de vinculação para extrair o URL de um atributo XML associado a uma ImageView e usar o Glide para carregar a imagem. Os adaptadores de vinculação são métodos de extensão que ficam entre uma visualização e os dados vinculados para fornecer um comportamento personalizado quando os dados mudam. Nesse caso, o comportamento personalizado é chamar o Glide para carregar uma imagem de um URL em uma ImageView.

  1. Abra BindingAdapters.kt. Esse arquivo conterá os adaptadores de vinculação que você usa em todo o app.
  2. Crie uma função bindImage() que receba uma ImageView e uma String como parâmetros. Adicione a anotação @BindingAdapter à função. A anotação @BindingAdapter informa à vinculação de dados que você quer que esse adaptador de vinculação seja executado quando um item XML tiver o atributo imageUrl.

    Importe androidx.databinding.BindingAdapter e android.widget.ImageView quando solicitado.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {

}
  1. Na função bindImage(), adicione um bloco let {} ao argumento imgUrl:
imgUrl?.let { 
}
  1. No bloco let {}, adicione a linha mostrada abaixo para converter a string de URL (do XML) em um objeto Uri. Importe androidx.core.net.toUri quando solicitado.

    Você quer que o objeto Uri final use o esquema HTTPS, porque o servidor de onde você extrai as imagens exige esse esquema. Para usar o esquema HTTPS, anexe buildUpon.scheme("https") ao builder toUri. O método toUri() é uma função de extensão Kotlin da biblioteca principal do Android KTX. Por isso, parece que ele faz parte da classe String.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
  1. Ainda no let {}, chame Glide.with() para carregar a imagem do objeto Uri no ImageView. Importe com.bumptech.glide.Glide quando solicitado.
Glide.with(imgView.context)
       .load(imgUri)
       .into(imgView)

Etapa 4: atualizar o layout e os fragmentos

Embora o Glide tenha carregado a imagem, ainda não há nada para ver. A próxima etapa é atualizar o layout e os fragmentos com um ImageView para mostrar a imagem.

  1. Abra res/layout/gridview_item.xml. Esse é o arquivo de recursos de layout que você vai usar para cada item no RecyclerView mais adiante no codelab. Você o usa temporariamente aqui para mostrar apenas a imagem única.
  2. Acima do elemento <ImageView>, adicione um elemento <data> para a vinculação de dados e vincule-o à classe OverviewViewModel:
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
  1. Adicione um atributo app:imageUrl ao elemento ImageView para usar o novo adaptador de vinculação de carregamento de imagem:
app:imageUrl="@{viewModel.property.imgSrcUrl}"
  1. Abra overview/OverviewFragment.kt. No método onCreateView(), use um comentário para excluir a linha que infla a classe FragmentOverviewBinding e a atribui à variável de vinculação. Isso é temporário. Você vai voltar a ele mais tarde.
//val binding = FragmentOverviewBinding.inflate(inflater)
  1. Adicione uma linha para inflar a classe GridViewItemBinding. Importe com.example.android.marsrealestate. databinding.GridViewItemBinding quando solicitado.
val binding = GridViewItemBinding.inflate(inflater)
  1. Execute o app. Agora você vai ver a foto da imagem do primeiro MarsProperty na lista de resultados.

Etapa 5: adicionar imagens simples de carregamento e erro

O Glide pode melhorar a experiência do usuário mostrando uma imagem de marcador enquanto a imagem real é carregada, ou exibir uma imagem de erro se o carregamento falhar, por exemplo, se a imagem não existir ou estiver corrompida. Nesta etapa, você vai adicionar essa funcionalidade ao adaptador de vinculação e ao layout.

  1. Abra res/drawable/ic_broken_image.xml e clique na guia Visualizar à direita. Para a imagem de erro, você usa o ícone de imagem corrompida que está disponível na biblioteca de ícones integrada. Esse drawable vetorial usa o atributo android:tint para colorir o ícone como cinza.

  1. Abra res/drawable/loading_animation.xml. Esse drawable é uma animação definida com a tag <animate-rotate>. A animação gira um drawable de imagem, loading_img.xml, ao redor do ponto central. Essa animação não vai ser mostrada na visualização.

  1. Retorne ao arquivo BindingAdapters.kt. No método bindImage(), atualize a chamada para Glide.with() e chame a função apply() entre load() e into(). Importe com.bumptech.glide.request.RequestOptions quando solicitado.

    Esse código define a imagem de carregamento de marcador a ser usada durante o carregamento (o drawable loading_animation). O código também define uma imagem que será usada se o carregamento da imagem falhar (o drawable broken_image). O método bindImage() completo agora ficará assim:
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
    imgUrl?.let {
        val imgUri = 
           imgUrl.toUri().buildUpon().scheme("https").build()
        Glide.with(imgView.context)
                .load(imgUri)
                .apply(RequestOptions()
                        .placeholder(R.drawable.loading_animation)
                        .error(R.drawable.ic_broken_image))
                .into(imgView)
    }
}
  1. Execute o app. Dependendo da velocidade da sua conexão de rede, você poderá ver brevemente o carregamento da imagem, à medida que o Glide transfere a imagem da propriedade e a exibe. No entanto, o ícone de imagem corrompida ainda não será mostrado, mesmo que você desative a rede. Isso será corrigido na última parte do codelab.

Agora seu app carrega informações de propriedades da Internet. Usando dados do primeiro item da lista MarsProperty, você criou uma propriedade LiveData no modelo de visualização e usou o URL da imagem desses dados de propriedade para preencher uma ImageView. Mas o objetivo é que o app mostre uma grade de imagens, então você vai usar um RecyclerView com um GridLayoutManager.

Etapa 1: atualizar o modelo de visualização

No momento, o modelo de visualização tem um _property LiveData que contém um objeto MarsProperty, o primeiro da lista de respostas do serviço da Web. Nesta etapa, você muda esse LiveData para armazenar a lista completa de objetos MarsProperty.

  1. Abra overview/OverviewViewModel.kt.
  2. Mude a variável privada _property para _properties. Mude o tipo para ser uma lista de objetos MarsProperty.
private val _properties = MutableLiveData<List<MarsProperty>>()
  1. Substitua os dados em tempo real externos property por properties. Adicione a lista ao tipo LiveData aqui também:
 val properties: LiveData<List<MarsProperty>>
        get() = _properties
  1. Role para baixo até encontrar o método getMarsRealEstateProperties(). Dentro do bloco try {}, substitua todo o teste que você adicionou na tarefa anterior pela linha mostrada abaixo. Como a variável listResult contém uma lista de objetos MarsProperty, basta atribuí-la a _properties.value em vez de testar uma resposta bem-sucedida.
_properties.value = listResult

O bloco try/catch como um todo ficará assim:

try {
   var listResult = getPropertiesDeferred.await()
   _response.value = "Success: ${listResult.size} Mars properties retrieved"
   _properties.value = listResult
} catch (e: Exception) {
   _response.value = "Failure: ${e.message}"
}

Etapa 2: atualizar os layouts e fragmentos

A próxima etapa é mudar o layout e os fragmentos do app para usar uma visualização de reciclagem e um layout de grade, em vez de uma única visualização de imagem.

  1. Abra res/layout/gridview_item.xml. Mude a vinculação de dados de OverviewViewModel para MarsProperty e renomeie a variável como "property".
<variable
   name="property"
   type="com.example.android.marsrealestate.network.MarsProperty" />
  1. Na <ImageView>, mude o atributo app:imageUrl para se referir ao URL da imagem no objeto MarsProperty:
app:imageUrl="@{property.imgSrcUrl}"
  1. Abra overview/OverviewFragment.kt. Em onCreateview(), remova a marca de comentário da linha que infla FragmentOverviewBinding. Exclua ou comente a linha que infla a GridViewBinding. Essas mudanças desfazem as temporárias feitas na tarefa anterior.
val binding = FragmentOverviewBinding.inflate(inflater)
 // val binding = GridViewItemBinding.inflate(inflater)
  1. Abra res/layout/fragment_overview.xml. Exclua todo o elemento <TextView>.
  2. Em vez disso, adicione este elemento <RecyclerView>, que usa um GridLayoutManager e o layout grid_view_item para um único item:
<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/photos_grid"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:padding="6dp"
            android:clipToPadding="false"
            app:layoutManager=
               "androidx.recyclerview.widget.GridLayoutManager"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:spanCount="2"
            tools:itemCount="16"
            tools:listitem="@layout/grid_view_item" />

Etapa 3: adicionar o adaptador de grade de fotos

Agora, o layout fragment_overview tem um RecyclerView, enquanto o layout grid_view_item tem um único ImageView. Nesta etapa, você vai vincular os dados à RecyclerView usando um adaptador da RecyclerView.

  1. Abra overview/PhotoGridAdapter.kt.
  2. Crie a classe PhotoGridAdapter com os parâmetros do construtor mostrados abaixo. A classe PhotoGridAdapter estende o ListAdapter, cujo construtor exige o tipo de item de lista, o armazenador de visualização e uma implementação de DiffUtil.ItemCallback.

    Importe as classes androidx.recyclerview.widget.ListAdapter e com.example.android.marsrealestate.network.MarsProperty quando solicitado. Nas etapas a seguir, você vai implementar as outras partes ausentes desse construtor que estão produzindo erros.
class PhotoGridAdapter : ListAdapter<MarsProperty,
        PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {

}
  1. Clique em qualquer lugar na classe PhotoGridAdapter e pressione Control+i para implementar os métodos ListAdapter, que são onCreateViewHolder() e onBindViewHolder().
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
   TODO("not implemented") 
}

override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
   TODO("not implemented") 
}
  1. No final da definição da classe PhotoGridAdapter, depois dos métodos que você acabou de adicionar, adicione uma definição de objeto complementar para DiffCallback, conforme mostrado abaixo.

    Importe androidx.recyclerview.widget.DiffUtil quando solicitado.

    O objeto DiffCallback estende o DiffUtil.ItemCallback com o tipo de objeto que você quer comparar, MarsProperty.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
  1. Pressione Control+i para implementar os métodos do comparador para esse objeto, que são areItemsTheSame() e areContentsTheSame().
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") 
}

override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") }
  1. No método areItemsTheSame(), remova o TODO. Use o operador de igualdade referencial do Kotlin (===), que retorna true se as referências de objeto para oldItem e newItem forem as mesmas.
override fun areItemsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem === newItem
}
  1. Para areContentsTheSame(), use o operador de igualdade padrão apenas no ID de oldItem e newItem.
override fun areContentsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem.id == newItem.id
}
  1. Ainda dentro da classe PhotoGridAdapter, abaixo do objeto complementar, adicione uma definição de classe interna para MarsPropertyViewHolder, que estende RecyclerView.ViewHolder.

    Importe androidx.recyclerview.widget.RecyclerView e com.example.android.marsrealestate.databinding.GridViewItemBinding quando solicitado.

    Você precisa da variável GridViewItemBinding para vincular MarsProperty ao layout. Portanto, transmita a variável para MarsPropertyViewHolder. Como a classe de base ViewHolder requer uma visualização no construtor, transmita a visualização raiz de vinculação a ela.
class MarsPropertyViewHolder(private var binding: 
                   GridViewItemBinding):
       RecyclerView.ViewHolder(binding.root) {

}
  1. No MarsPropertyViewHolder, crie um método bind() que recebe um objeto MarsProperty como argumento e define a binding.property para esse objeto. Chame executePendingBindings() depois de definir a propriedade, o que fará com que a atualização seja executada imediatamente.
fun bind(marsProperty: MarsProperty) {
   binding.property = marsProperty
   binding.executePendingBindings()
}
  1. Em onCreateViewHolder(), remova o TODO e adicione a linha mostrada abaixo. Importe android.view.LayoutInflater quando solicitado.

    O método onCreateViewHolder() precisa retornar um novo MarsPropertyViewHolder, criado inflando a GridViewItemBinding e usando o LayoutInflater do contexto ViewGroup pai.
   return MarsPropertyViewHolder(GridViewItemBinding.inflate(
      LayoutInflater.from(parent.context)))
  1. No método onBindViewHolder(), remova o código marcado como "TODO" e adicione as linhas mostradas abaixo. Aqui, você chama getItem() para receber o objeto MarsProperty associado à posição atual do RecyclerView e, em seguida, transmite essa propriedade para o método bind() no MarsPropertyViewHolder.
val marsProperty = getItem(position)
holder.bind(marsProperty)

Etapa 4: adicionar o adaptador de vinculação e conectar as partes

Por fim, use um BindingAdapter para inicializar o PhotoGridAdapter com a lista de objetos MarsProperty. Usar um BindingAdapter para definir os dados do RecyclerView faz com que a vinculação de dados observe automaticamente o LiveData da lista de objetos MarsProperty. O adaptador de vinculação será chamado automaticamente quando a lista do MarsProperty mudar.

  1. Abra o arquivo BindingAdapters.kt.
  2. No final do arquivo, adicione um método bindRecyclerView() que usa um RecyclerView e uma lista de objetos MarsProperty como argumentos. Anote esse método com um @BindingAdapter.

    Importe androidx.recyclerview.widget.RecyclerView e com.example.android.marsrealestate.network.MarsProperty quando solicitado.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, 
    data: List<MarsProperty>?) {
}
  1. Na função bindRecyclerView(), transmita recyclerView.adapter para PhotoGridAdapter e chame adapter.submitList() com os dados. Isso informa o RecyclerView quando uma nova lista está disponível.

Importe com.example.android.marsrealestate.overview.PhotoGridAdapter quando solicitado.

val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
  1. Abra res/layout/fragment_overview.xml. Adicione o atributo app:listData ao elemento RecyclerView e defina-o como viewmodel.properties usando a vinculação de dados.
app:listData="@{viewModel.properties}"
  1. Abra overview/OverviewFragment.kt. Em onCreateView(), logo antes da chamada para setHasOptionsMenu(), inicialize o adaptador RecyclerView em binding.photosGrid para um novo objeto PhotoGridAdapter.
binding.photosGrid.adapter = PhotoGridAdapter()
  1. Execute o app. Você vai ver uma grade de imagens de MarsProperty. Ao rolar para ver novas imagens, o app mostra o ícone de progresso do carregamento antes de exibir a imagem em si. Se você ativar o modo avião, as imagens que ainda não foram carregadas vão aparecer como ícones de imagem corrompida.

O app MarsRealEstate mostra o ícone de imagem corrompida quando uma imagem não pode ser buscada. No entanto, quando não há rede, o app mostra uma tela em branco.

Essa não é uma ótima experiência do usuário. Nesta tarefa, você adicionará um processamento básico de erros para dar ao usuário uma ideia melhor do que está acontecendo. Se a Internet não estiver disponível, o app vai mostrar o ícone de erro de conexão. Enquanto o app busca a lista de MarsProperty, uma animação de carregamento é exibida.

Etapa 1: adicionar status ao modelo de visualização

Para começar, crie um LiveData no modelo de visualização para representar o status da solicitação da Web. Há três estados a serem considerados: carregamento, sucesso e falha. O estado de carregamento acontece enquanto você aguarda os dados na chamada para await().

  1. Abra overview/OverviewViewModel.kt. Na parte superior do arquivo (após as importações e antes da definição das classes), adicione uma enum para representar todos os status disponíveis:
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. Renomeie as definições de dados ativos _response internas e externas em toda a classe OverviewViewModel para _status. Como você adicionou suporte para o _properties LiveData no início deste codelab, a resposta completa do serviço da Web não foi usada. Você precisa de um LiveData aqui para acompanhar o status atual. Basta renomear as variáveis atuais.

Além disso, mude os tipos de String para MarsApiStatus..

private val _status = MutableLiveData<MarsApiStatus>()

val status: LiveData<MarsApiStatus>
   get() = _status
  1. Role para baixo até o método getMarsRealEstateProperties() e atualize _response para _status aqui também. Mude a string "Success" para o estado MarsApiStatus.DONE e a string "Failure" para MarsApiStatus.ERROR.
  2. Adicione um status MarsApiStatus.LOADING à parte de cima do bloco try {}, antes da chamada para await(). Esse será o status inicial enquanto a corrotina está em execução e você está aguardando os dados. O bloco try/catch {} completo agora ficará assim:
try {
    _status.value = MarsApiStatus.LOADING
   var listResult = getPropertiesDeferred.await()
   _status.value = MarsApiStatus.DONE
   _properties.value = listResult
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
}
  1. Depois do estado de erro no bloco catch {}, defina _properties LiveData como uma lista vazia. Isso limpa o RecyclerView.
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
   _properties.value = ArrayList()
}

Etapa 2: adicionar um adaptador de vinculação ao status da ImageView

Agora você tem um status no modelo de visualização, mas ele é apenas um conjunto de estados. Como fazer com que ele apareça no próprio app? Nesta etapa, você usa uma ImageView, conectada à vinculação de dados, para mostrar ícones dos estados de carregamento e erro. Quando o app estiver no estado de carregamento ou no estado de erro, a ImageView precisará estar visível. Quando o app terminar de carregar, a ImageView ficará invisível.

  1. Abra BindingAdapters.kt. Adicione um novo adaptador de vinculação com o nome bindStatus() que usa os valores ImageView e MarsApiStatus como argumentos. Importe com.example.android.marsrealestate.overview.MarsApiStatus quando solicitado.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView, 
          status: MarsApiStatus?) {
}
  1. Adicione um when {} ao método bindStatus() para alternar entre os diferentes status.
when (status) {

}
  1. No método when {}, adicione um caso para o estado de carregamento (MarsApiStatus.LOADING). Para esse estado, defina a ImageView como visível e atribua a animação de carregamento. Esse é o mesmo drawable de animação que você usou para o Glide na tarefa anterior. Importe android.view.View quando solicitado.
when (status) {
   MarsApiStatus.LOADING -> {
      statusImageView.visibility = View.VISIBLE
      statusImageView.setImageResource(R.drawable.loading_animation)
   }
}
  1. Adiciona um caso para o estado de erro, que é MarsApiStatus.ERROR. Da mesma forma que você fez para o estado LOADING, defina o status da ImageView como visível e reutilize o drawable de erro de conexão.
MarsApiStatus.ERROR -> {
   statusImageView.visibility = View.VISIBLE
   statusImageView.setImageResource(R.drawable.ic_connection_error)
}
  1. Adicione um caso para o estado concluído, que é MarsApiStatus.DONE. Quando você receber uma resposta bem-sucedida, desative a visibilidade do status da ImageView para ocultá-la.
MarsApiStatus.DONE -> {
   statusImageView.visibility = View.GONE
}

Etapa 3: adicionar o status da ImageView ao layout

  1. Abra res/layout/fragment_overview.xml. Abaixo do elemento RecyclerView, dentro do ConstraintLayout, adicione a ImageView mostrada abaixo.

    Essa ImageView tem as mesmas restrições que o RecyclerView. No entanto, a largura e a altura usam wrap_content para centralizar a imagem em vez de esticá-la para preencher a visualização. O atributo app:marsApiStatus, que faz com que a visualização chame o BindingAdapter quando a propriedade de status no modelo de visualização muda.
<ImageView
   android:id="@+id/status_image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:marsApiStatus="@{viewModel.status}" />
  1. Ative o modo avião no emulador ou dispositivo para simular uma conexão de rede ausente. Compile e execute o app. Observe a imagem de erro exibida:

  1. Toque no botão "Voltar" para fechar o app e desativar o modo avião. Use a tela Recentes para retornar ao app. Dependendo da velocidade da conexão de rede, será possível ver um ícone de carregamento rapidamente quando o app consultar o serviço da Web antes que as imagens comecem a carregar.

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

  • Para simplificar o processo de gerenciamento de imagens, use a biblioteca Glide para fazer o download, armazenar em buffer, decodificar e armazenar em cache imagens no seu app.
  • O Glide precisa de duas coisas para carregar uma imagem da Internet: o URL de uma imagem e um objeto ImageView para colocar a imagem. Para especificar essas opções, use os métodos load() e into() com o Glide.
  • Adaptadores de vinculação são métodos de extensão que ficam entre uma visualização e os dados vinculados dela. Adaptadores de vinculação oferecem comportamento personalizado quando os dados mudam, por exemplo, para chamar o Glide para carregar uma imagem de um URL em uma ImageView.
  • Adaptadores de vinculação são métodos de extensão anotados com @BindingAdapter.
  • Para adicionar opções à solicitação do Glide, use o método apply(). Por exemplo, use apply() com placeholder() para especificar um elemento combinável de carregamento e apply() com error() para especificar um elemento combinável de erro.
  • Para produzir uma grade de imagens, use um RecyclerView com um GridLayoutManager.
  • Para atualizar a lista de propriedades quando ela mudar, use um adaptador de vinculação entre o RecyclerView e o layout.

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

Qual método do Glide usar para indicar a ImageView que vai conter a imagem carregada?

into()

with()

imageview()

apply()

Pergunta 2

Como especificar uma imagem de marcador para ser exibida quando o Glide estiver sendo carregado?

▢ Use o método into() com um drawable.

▢ Use RequestOptions() e chame o método placeholder() com um drawable.

▢ Atribua a propriedade Glide.placeholder a um drawable.

▢ Use RequestOptions() e chame o método loadingImage() com um drawable.

Pergunta 3

Como indicar que um método é um adaptador de vinculação?

▢ Chame o método setBindingAdapter() no LiveData.

▢ Coloque o método em um arquivo Kotlin chamado BindingAdapters.kt.

▢ Use o atributo android:adapter no layout XML.

▢ Adicione a anotação @BindingAdapter ao método.

Comece a próxima lição: 8.3 Como filtrar e detalhar visualizações com dados da Internet

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