Criar um app Android com visualizações

1. Antes de começar

Introdução

Até agora, você aprendeu tudo sobre a criação de apps Android com o Compose. Isso é ótimo! O Compose é uma ferramenta muito eficiente que pode simplificar o processo de desenvolvimento. No entanto, os apps Android nem sempre foram criados com interfaces declarativas. O Compose é uma ferramenta bem recente na história dos apps Android. Originalmente, as interfaces do Android eram criadas com visualizações. Então, é muito provável que você encontre visualizações ao continuar sua jornada como desenvolvedor Android. Neste codelab, você vai aprender as noções básicas de como os apps Android eram criados antes do Compose, com XML, visualizações, vinculações de visualização e fragmentos.

Pré-requisitos:

  • Concluir o curso "Noções básicas do Android no Compose" até a Unidade 7.

O que é necessário

  • Um computador com acesso à Internet e o Android Studio.
  • Um dispositivo ou emulador.
  • O código inicial do app Juice Tracker.

O que você vai criar

Neste codelab, você vai concluir o app Juice Tracker. Com ele, você pode conferir sucos incríveis, criando uma lista com itens detalhados. Adicione e modifique os fragmentos e XML para concluir a interface e o código inicial. Especificamente, você cria o formulário de registro para criar um suco, incluindo a interface e qualquer lógica ou navegação associada. O resultado é um app com uma lista vazia à qual você pode adicionar seus sucos.

d6dc43171ae62047.png 87b2ca7b49e814cb.png 2d630489477e216e.png

2. Acessar o código inicial

  1. No Android Studio, abra a pasta basic-android-kotlin-compose-training-juice-tracker.
  2. Abra o código do app Juice Tracker no Android Studio.

3. Criar um layout

Ao criar um app com Views, você constrói a interface dentro de um layout. Os layouts geralmente são declarados usando XML. Esses arquivos XML estão localizados no diretório de recursos em res > layout. Os layouts contêm os componentes da interface, conhecidos como Views. A sintaxe XML consiste em tags, elementos e atributos. Para mais detalhes sobre a sintaxe XML, consulte o codelab Criar layouts XML para Android.

Nesta seção, você vai criar um layout XML para a caixa de diálogo de registro Type of juice (tipo de suco) que aparece na imagem.

87b2ca7b49e814cb.png

  1. Crie um novo Layout Resource File no diretório main > res > layout com o nome fragment_entry_dialog.

Contexto do painel de projeto do Android Studio aberto com uma opção para criar um arquivo de recurso de layout.

6adb279d6e74ab13.png

O layout fragment_entry_dialog.xml contém os componentes de interface que o app mostra ao usuário.

Observe que o elemento raiz é um ConstraintLayout. Esse tipo de layout é um ViewGroup que permite posicionar e dimensionar visualizações de maneira flexível usando as restrições. Um ViewGroup é um tipo de View que contém outras Views, chamadas de filhas ou Views filhas. As etapas abaixo abordam esse assunto mais detalhadamente, mas você pode saber mais sobre o ConstraintLayout em Como criar uma interface responsiva com o ConstraintLayout.

  1. Depois de criar o arquivo, defina o espaço de nome do app no ConstraintLayout.

fragment_entry_dialog.xml

<androidx.constraintlayout.widget.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
  1. Adicione as diretrizes abaixo ao ConstraintLayout.

fragment_entry_dialog.xml

<androidx.constraintlayout.widget.Guideline
   android:id="@+id/guideline_left"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:orientation="vertical"
   app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
   android:id="@+id/guideline_middle"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:orientation="vertical"
   app:layout_constraintGuide_percent="0.5" />
<androidx.constraintlayout.widget.Guideline
   android:id="@+id/guideline_top"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:orientation="horizontal"
   app:layout_constraintGuide_begin="16dp" />

Essas Guidelines servem como padding para outras visualizações. As diretrizes restringem o texto do cabeçalho Type of juice.

  1. Crie um elemento TextView, que representa o título do fragmento de detalhes.

110cad4ae809e600.png

  1. Defina a TextView como um id de header_title.
  2. Defina layout_width como 0dp. As restrições de layout definem a largura dessa TextView. Portanto, a definição de uma largura só adiciona cálculos desnecessários durante o desenho da interface. Definir uma largura de 0dp evita os cálculos extras.
  3. Defina o atributo TextView text como @string/juice_type.
  4. Defina a textAppearance como @style/TextAppearance.MaterialComponents.Headline5.

fragment_entry_dialog.xml

<TextView
   android:id="@+id/header_title"
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   android:text="@string/juice_type"
   android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />

Por fim, é preciso definir as restrições. Ao contrário das Guidelines, que usam dimensões como restrições, as diretrizes restringem essa TextView. Para alcançar esse resultado, você pode referenciar o ID da Guideline pela qual quer restringir a visualização.

  1. Restrinja a parte de cima do cabeçalho à parte de baixo de guideline_top.

fragment_entry_dialog.xml

<TextView
   android:id="@+id/header_title"
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   android:text="@string/juice_type"
   android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
   app:layout_constraintTop_toBottomOf="@+id/guideline_top" />
  1. Restrinja o final ao início da guideline_middle, e o início ao início da guideline_left para concluir o posicionamento da TextView. Vale lembrar que a restrição uma determinada visualização depende totalmente de como você quer a aparência da interface.

fragment_entry_dialog.xml

<TextView
   android:id="@+id/header_title"
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   android:text="@string/juice_type"
   android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
   app:layout_constraintTop_toBottomOf="@+id/guideline_top"
   app:layout_constraintEnd_toStartOf="@+id/guideline_middle"
   app:layout_constraintStart_toStartOf="@+id/guideline_left" />

Tente criar o restante da interface com base nas capturas de tela. O arquivo fragment_entry_dialog.xml completo está disponível na solução (links em inglês).

4. Criar um fragmento com visualizações

No Compose, você cria layouts de forma declarativa usando Kotlin ou Java. É possível acessar diferentes "telas" navegando para diferentes elementos combináveis, geralmente dentro da mesma atividade. Ao criar um app com visualizações, um fragmento que hospeda o layout XML substitui o conceito de uma "tela" combinável.

Nesta seção, você cria um Fragment para hospedar o layout do fragment_entry_dialog e fornecer dados à interface.

  1. No pacote juicetracker, crie uma nova classe com o nome EntryDialogFragment.
  2. Faça com que o EntryDialogFragment estenda o BottomSheetDialogFragment.

EntryDialogFragment.kt

import com.google.android.material.bottomsheet.BottomSheetDialogFragment

class EntryDialogFragment : BottomSheetDialogFragment() {
}

O DialogFragment é um Fragment que mostra uma caixa de diálogo flutuante. O BottomSheetDialogFragment é herdado da classe DialogFragment, mas mostra uma página com a largura da tela fixada na parte de baixo. Essa abordagem corresponde ao design ilustrado anteriormente.

  1. Recrie o projeto, fazendo com que os arquivos de vinculação de visualizações baseados no layout fragment_entry_dialog sejam gerados automaticamente. As vinculações de visualizações permitem acessar e interagir com Views declaradas em XML. Saiba mais sobre elas na documentação Vinculação de visualizações.
  2. Implemente a função onCreateView() na classe EntryDialogFragment. Como o nome sugere, essa função cria a View para esse Fragment.

EntryDialogFragment.kt

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

override fun onCreateView(
   inflater: LayoutInflater,
   container: ViewGroup?,
   savedInstanceState: Bundle?
): View? {
   return super.onCreateView(inflater, container, savedInstanceState)
}

A função onCreateView() retorna uma View, mas no momento, não retorna uma View útil.

  1. Retorne a View gerada inflando o FragmentEntryDialogViewBinding em vez de retornar super.onCreateView().

EntryDialogFragment.kt

import com.example.juicetracker.databinding.FragmentEntryDialogBinding

override fun onCreateView(
   inflater: LayoutInflater,
   container: ViewGroup?,
   savedInstanceState: Bundle?
): View? {
   return FragmentEntryDialogBinding.inflate(inflater, container, false).root
}
  1. Fora da função onCreateView(), mas dentro da classe EntryDialogFragment, crie uma instância do EntryViewModel.
  2. Implemente a função onViewCreated().

Depois de inflar a vinculação de visualizações, você pode acessar e modificar as Views no layout. O método onViewCreated() é chamado depois do onCreateView() no ciclo de vida. O método onViewCreated() é o local recomendado para acessar e modificar as Views no layout.

  1. Crie uma instância da vinculação de visualizações chamando o método bind() no FragmentEntryDialogBinding.

Neste ponto, seu código vai estar parecido com este exemplo:

EntryDialogFragment.kt

import androidx.fragment.app.viewModels
import com.example.juicetracker.ui.AppViewModelProvider
import com.example.juicetracker.ui.EntryViewModel

class EntryDialogFragment : BottomSheetDialogFragment() {

   private val entryViewModel by viewModels<EntryViewModel> { AppViewModelProvider.Factory }

   override fun onCreateView(
       inflater: LayoutInflater,
       container: ViewGroup?,
       savedInstanceState: Bundle?
   ): View {
       return FragmentEntryDialogBinding.inflate(inflater, container, false).root
   }

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val binding = FragmentEntryDialogBinding.bind(view)
    }
}

É possível acessar e definir visualizações com a vinculação. Por exemplo, você pode definir uma TextView usando o método setText().

binding.name.setText("Apple juice")

A interface da caixa de diálogo de registro serve como um lugar para um usuário criar um item, mas também pode ser usada para modificar um item já existente. Portanto, o fragmento precisa recuperar um item clicado. O componente de navegação facilita a navegação até EntryDialogFragment e a recuperação de um item clicado.

A classe EntryDialogFragment ainda não foi concluída, mas não se preocupe. Vá para a próxima seção para saber mais sobre como usar o componente de navegação em um app com Views.

5. Modificar o componente de navegação

Nesta seção, você vai usar o componente de navegação para iniciar a caixa de diálogo de registro e acessar um item, se for o caso.

O Compose oferece a oportunidade de renderizar diferentes elementos combináveis simplesmente chamando esses elementos. No entanto, os fragmentos funcionam de forma diferente. O componente de navegação coordena os "destinos" dos fragmentos, oferecendo uma maneira fácil de acessar diferentes fragmentos e as visualizações que eles contêm.

Use o componente de navegação para coordenar a navegação com sua classe EntryDialogFragment.

  1. Abra o arquivo nav_graph.xml e verifique se a guia Design está selecionada. 783cb5d7ff0ba127.png
  2. Clique no ícone 93401bf098936c15.png para adicionar um destino.

d5410c90e408b973.png

  1. Selecione o destino EntryDialogFragment. Essa ação declara a classe entryDialogFragment no gráfico de navegação, deixando-a acessível para ações de navegação.

418feed425072ea4.png

É necessário iniciar a EntryDialogFragment no TrackerFragment. Portanto, uma ação de navegação precisa realizar essa tarefa.

  1. Passe o cursor sobre trackerFragment. Selecione o ponto cinza e arraste a linha até entryDialogFragment. 85decb6fcddec713.png
  2. A visualização de design nav_graph permite declarar argumentos de um destino selecionando o destino e clicando no ícone a0d73140a20e4348.png ao lado do menu suspenso Arguments. Use esse recurso para adicionar um argumento itemId do tipo Long à entryDialogFragment. O valor padrão precisa ser 0L.

555cf791f64f62b8.png

840105bd52f300f7.png

O TrackerFragment contém uma lista de itens Juice. Quando você clica em um deles, a EntryDialogFragment é iniciada.

  1. Recrie o projeto. O argumento itemId agora pode ser acessado na classe EntryDialogFragment.

6. Concluir o fragmento

Com os dados dos argumentos de navegação, preencha a caixa de diálogo de registro.

  1. Acesse o navArgs() no método onViewCreated() da EntryDialogFragment.
  2. Extraia o itemId do navArgs().
  3. Implemente o saveButton para salvar o suco novo/modificado usando o ViewModel.

Não se esqueça que, na interface da caixa de diálogo de registro, a cor padrão é o vermelho. Por enquanto, transmita isso como um holder de lugar.

Transmita o ID do item dos argumentos ao chamar saveJuice().

EntryDialogFragment.kt

import androidx.navigation.fragment.navArgs
import com.example.juicetracker.data.JuiceColor

class EntryDialogFragment : BottomSheetDialogFragment() {

   //...
   var selectedColor: JuiceColor = JuiceColor.Red

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val binding = FragmentEntryDialogBinding.bind(view)
        val args: EntryDialogFragmentArgs by navArgs()
        val juiceId = args.itemId

        binding.saveButton.setOnClickListener {
           entryViewModel.saveJuice(
               juiceId,
               binding.name.text.toString(),
               binding.description.text.toString(),
               selectedColor.name,
               binding.ratingBar.rating.toInt()
           )
        }
    }
}
  1. Depois que os dados forem salvos, dispense a caixa de diálogo com o método dismiss().

EntryDialogFragment.kt

class EntryDialogFragment : BottomSheetDialogFragment() {

    //...
    var selectedColor: JuiceColor = JuiceColor.Red
    //...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val binding = FragmentEntryDialogBinding.bind(view)
        val args: EntryDialogFragmentArgs by navArgs()
        binding.saveButton.setOnClickListener {
           entryViewModel.saveJuice(
               juiceId,
               binding.name.text.toString(),
               binding.description.text.toString(),
               selectedColor.name,
               binding.ratingBar.rating.toInt()
           )
           dismiss()
        }
    }
}

O código acima não conclui a EntryDialogFragment. Você ainda precisa implementar vários elementos, como o preenchimento dos campos com dados de Juice (se relevante), a seleção de uma cor no colorSpinner, a implementação do cancelButton, entre outros. No entanto, esse código não é exclusivo para Fragments, e pode ser implementado por você. Tente implementar o restante da funcionalidade. Como último recurso, você pode consultar o código da solução deste codelab.

7. Abrir a caixa de diálogo de registro

A última tarefa é iniciar a caixa de diálogo de registro usando o componente de navegação. Essa caixa de diálogo precisa ser iniciada quando o usuário clica no botão de ação flutuante (FAB). Ela também precisa iniciar e transmitir o ID correspondente quando o usuário clica em um item.

  1. No onClickListener() do FAB, chame navigate() no controlador de navegação.

TrackerFragment.kt

import androidx.navigation.findNavController

//...

binding.fab.setOnClickListener { fabView ->
   fabView.findNavController().navigate(
   )
}

//...
  1. Na função de navegação, transmita a ação para navegar do monitoramento até a caixa de diálogo de registro.

TrackerFragment.kt

//...

binding.fab.setOnClickListener { fabView ->
   fabView.findNavController().navigate(
TrackerFragmentDirections.actionTrackerFragmentToEntryDialogFragment()
   )
}

//...
  1. Repita essa ação no corpo da lambda para o método onEdit() no JuiceListAdapter, mas desta vez, transmita o id do Juice.

TrackerFragment.kt

//...

onEdit = { drink ->
   findNavController().navigate(
       TrackerFragmentDirections.actionTrackerFragmentToEntryDialogFragment(drink.id)
   )
},

//...

8. Acessar o código da solução

Para baixar o código do codelab concluído, use estes comandos git:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views

Se preferir, você pode baixar o repositório como um arquivo ZIP, descompactar e abrir no Android Studio.

Se você quiser conferir o código da solução, acesse o GitHub (em inglês).