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.
2. Acessar o código inicial
- No Android Studio, abra a pasta
basic-android-kotlin-compose-training-juice-tracker
. - 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 View
s. 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.
- Crie um novo Layout Resource File no diretório main > res > layout com o nome
fragment_entry_dialog
.
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 View
s, chamadas de filhas ou View
s 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.
- 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>
- 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 Guideline
s servem como padding para outras visualizações. As diretrizes restringem o texto do cabeçalho Type of juice.
- Crie um elemento
TextView
, que representa o título do fragmento de detalhes.
- Defina a
TextView
como umid
deheader_title
. - Defina
layout_width
como0dp
. As restrições de layout definem a largura dessaTextView
. Portanto, a definição de uma largura só adiciona cálculos desnecessários durante o desenho da interface. Definir uma largura de0dp
evita os cálculos extras. - Defina o atributo
TextView text
como@string/juice_type
. - 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 Guideline
s, 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.
- 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" />
- Restrinja o final ao início da
guideline_middle
, e o início ao início daguideline_left
para concluir o posicionamento daTextView
. 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.
- No pacote
juicetracker
, crie uma nova classe com o nomeEntryDialogFragment
. - Faça com que o
EntryDialogFragment
estenda oBottomSheetDialogFragment
.
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.
- 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 comView
s declaradas em XML. Saiba mais sobre elas na documentação Vinculação de visualizações. - Implemente a função
onCreateView()
na classeEntryDialogFragment
. Como o nome sugere, essa função cria aView
para esseFragment
.
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.
- Retorne a
View
gerada inflando oFragmentEntryDialogViewBinding
em vez de retornarsuper.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
}
- Fora da função
onCreateView()
, mas dentro da classeEntryDialogFragment
, crie uma instância doEntryViewModel
. - Implemente a função
onViewCreated()
.
Depois de inflar a vinculação de visualizações, você pode acessar e modificar as View
s 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 View
s no layout.
- Crie uma instância da vinculação de visualizações chamando o método
bind()
noFragmentEntryDialogBinding
.
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 View
s.
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
.
- Abra o arquivo
nav_graph.xml
e verifique se a guia Design está selecionada. - Clique no ícone para adicionar um destino.
- Selecione o destino
EntryDialogFragment
. Essa ação declara a classeentryDialogFragment
no gráfico de navegação, deixando-a acessível para ações de navegação.
É necessário iniciar a EntryDialogFragment
no TrackerFragment
. Portanto, uma ação de navegação precisa realizar essa tarefa.
- Passe o cursor sobre
trackerFragment
. Selecione o ponto cinza e arraste a linha atéentryDialogFragment
. - A visualização de design nav_graph permite declarar argumentos de um destino selecionando o destino e clicando no ícone ao lado do menu suspenso Arguments. Use esse recurso para adicionar um argumento
itemId
do tipoLong
àentryDialogFragment
. O valor padrão precisa ser0L
.
O TrackerFragment
contém uma lista de itens Juice
. Quando você clica em um deles, a EntryDialogFragment
é iniciada.
- Recrie o projeto. O argumento
itemId
agora pode ser acessado na classeEntryDialogFragment
.
6. Concluir o fragmento
Com os dados dos argumentos de navegação, preencha a caixa de diálogo de registro.
- Acesse o
navArgs()
no métodoonViewCreated()
daEntryDialogFragment
. - Extraia o
itemId
donavArgs()
. - Implemente o
saveButton
para salvar o suco novo/modificado usando oViewModel
.
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()
)
}
}
}
- 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 Fragment
s, 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.
- No
onClickListener()
do FAB, chamenavigate()
no controlador de navegação.
TrackerFragment.kt
import androidx.navigation.findNavController
//...
binding.fab.setOnClickListener { fabView ->
fabView.findNavController().navigate(
)
}
//...
- 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()
)
}
//...
- Repita essa ação no corpo da lambda para o método
onEdit()
noJuiceListAdapter
, mas desta vez, transmita oid
doJuice
.
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).