Firebase App - Parte 2
| Site: | IFSC |
| Course: | 2025-2 - PROGRAMAÇÃO PARA DISPOSITIVOS MÓVEIS |
| Book: | Firebase App - Parte 2 |
| Printed by: | Guest user |
| Date: | Friday, 19 December 2025, 3:13 PM |
1. Apresentação
Neste livro conheceremos novos componentes avançados para a construção de aplicativos usando o Android Jetpack. Vamos exemplificar o uso dos componentes de arquitetura (Android Arch Components) do Android Jetpack e o uso do Firebase através do aplicativo Lista de Tarefas.
O aplicativo Lista de Tarefas fornece uma maneira de controlar nossas atividades, mostrando uma lista de tarefas e as opções de marcar como concluída. No aplicativo podemos:
- Criar um usuário
- Cadastrar tarefa
- Marcar tarefa como finalizada
- Remover tarefa
Figura 1 Aplicativo
Devido a complexidade desse projeto e a quantidade de recursos novos a serem aprendidos o livro vai ser organizado em três partes:
- Parte 1 – Criação da interface gráfica com Fragmentos, RecyclerView, Material Icons e Menus. Integração com FIrebase e cadastro de usuários. Configuração das transições entre as telas (fragmentos) com o componente de navegação (Navigation) .
- Parte 2 - lista de tarefas e RecyclerView
Bons estudos!
2. Recursos Envolvidos
No desenvolvimento da primeira parte do aplicativo, vamos usar as seguintes tecnologias:.
- RecyclerView, RecyclerViewAdapter e padrão ViewHolder
- Este aplicativo exibe uma lista de contatos com um componente RecyclerView (pacote androidx.recyclerview) – uma lista rolante de itens reutilizável. Para preencher essa lista vamos precisar criar uma subclasse de RecyclerViewAdapter, cuja finalidade é preencher o elemento RecyclerView utilizando dados de um objeto ArrayList.
- Quando o aplicativo atualiza os dados da lista de contatos ele chama o método notifyDataSetChanged do RecyclerViewAdapter para indicar que os dados mudaram. Então, o adaptador notificará o componente RecyclerView para que atualize sua lista de itens exibidos. Isso é conhecido como vinculação de dados (data binding).
- Cada item adicionado à lista envolve a execução do processo de criar novos objetos dinamicamente. Para listas grandes, nos quais o usuário rola rapidamente, a quantidade de itens gera uma sobrecarga que pode impedir uma rolagem suave. Logo, para reduzir essa sobrecarga, quando os itens do componente RecyclerView rolam para fora da tela, vamos fazer a reutilização desses itens de lista para os novos que estão entrando na tela. Para isso, usamos o padrão ViewHolder, no qual criamos uma classe (normalmente chamada ViewHolder) para conter variáveis de instância para as views que exibem os dados dos itens na RecyclerView.
- FloatingActionButton
- Os botões de ação flutuantes, comumente chamados de FAB (FloatingActionButton), foram introduzidos pelo Material Design e são botões que “flutuam”, isto é, possuem elevação maior que o restante dos elementos da interface gráfica do aplicativo. A partir do Android 6.0, esse componente passou a ser disponibilizado pela Android Design Support Library.
- É comum usar esse botão para uma ação única, mas importante para o aplicativo. Alguns exemplos de seu uso: enviar um email (Gmail) e, no caso da Agenda de Contatos, ir para a tela de adição de um novo contato.
- FloatingActionButton é uma subclasse de ImageView, portanto, é possível usar esse botão para exibir uma imagem. Dessa forma, podemos usar ícones do Material Design nos FAB’s.
- As diretrizes do Material Design sugerem posicionar esses botões a pelo menos 16dp das margens do celular e a pelo menos 24dp das margens em um tablet. Naturalmente, o Android Studio configura esses valores padrões aos botões do projeto, mas nada impede deles serem modificados.
- Material Design Icons
- O Android Studio oferece uma ferramenta chamada Vector Asset para adicionar ao projeto os ícones disponíveis do Material Design. A lista de ícones é grande e abrange desde ícones de navegações, para ações, acessibilidade, arquivos, play e muitos outros.
- Esses ícones são imagens vetoriais capazes de serem redimensionadas para qualquer resolução sem perder a qualidade pois não são exatamente imagens, mas sim definições de formas. Eles também podem ser configurados para diferentes cores.
- Menus
- Menus podem ser adicionados em atividades ou fragmentos. Eles são incluídos na barra do aplicativo (ToolBar) e podem assumir forma de um ícone com uma lista de opções (menu) ou oferecer diretamente ícones para realizar as ações. Esse ícone fica normalmente no lado direita da barra e usa um arquivo do tipo menu.xml para definir seu comportamento.
- Neste projeto iremos configurar um Menu com duas opções: editar e excluir um contato.
- Android Jetpack
- O Android Jetpack é uma coleção de componentes de software Android que buscam facilitar o desenvolvimento de excelentes aplicativos Android. Esses componentes ajudam você a seguir as práticas recomendadas, acabando com os códigos clichê e simplificando tarefas complexas, para que você possa se concentrar na parte do código do seu interesse.
- O Jetpack é composto por bibliotecas de pacotes do “androidx.*”, separadas das APIs da plataforma. Isso significa que ele oferece retrocompatibilidade e é atualizado com mais frequência do que a plataforma Android, garantindo que você sempre tenha acesso às versões mais recentes dos componentes do Jetpack.
- Alguns componentes de arquitetura Android Arch Components:
- Fragment: uma unidade básica de interface gráfica combinável
- ViewModel: gerencia os dados para interface gráfica considerando o ciclo de vida
- LifeCycles: para gerenciar os ciclos de vida de atividade e fragmentos
- LiveData: para manipular as informações do banco de dados
- Room: biblioteca para facilitar o acesso ao banco de dados SQLite
Nos próximos partes do projeto será documentado de maneira mais profundos os recursos envolvidos em cada etapa.
3. Configurações iniciais do projeto
Nesta primeira parte do projeto vamos construir o desenho da interface gráfica e a configuração dos recursos necessários para a construção do aplicativo.
Vamos realizar as seguintes etapas:
- Integração ao Firebase com o Android Studio
- Criação das classes e fragmentos
- Configuração de ícones, recursos strings e estilos de borda
3.1. Criação das classes do aplicativo
Para a continuação do aplicativo, vamos precisar de novas classes para a implementação das funcionalidades da Lista de Tarefa.
Crie as seguintes classes:
- ListaTarefaAdapter
- Classe responsável por preencher e interagir com itens de uma RecyclerView (lista)
- Tarefa
- Data class para representar os dados de uma tarefa
- TarefaRepository
- Classe para encapsular todas as formas de se comunicar com o Firebase para realizar as operações CRUD (Create, Update, Delete e Remove)
O projeto deve estar configura com as seguintes classes:
3.2. Adicionando ícones
O Material Design opera nativamente no Android Studio e oferece diferentes tipos de recursos para serem incluídos nas aplicações Android. Um desses recursos é o uso de ícones. O Material Design Icons é um kit com diversos tipos de ícones e está presente dentro do Android Studio pronto para ser utilizado. A vantagem de se usar esses ícones é que eles são imagens vetoriais, isto é, não perdem a qualidade ao escalar o tamanho na tela.
Como adicionar ícones ao projeto
Para adicionar um novo ícone ao projeto no Android Studio, siga os seguintes passos:
- Selecione File > New > Vector Asset para abrir a ferramenta Vector Asset Studio
Figura 14 Caminho para a ferramenta Vector Asset
- Clique no botão ao lado do campo Clip Art e na próxima janela procure o ícone com nome “save”, clique em OK

Figura 15 Selecionando o icone "save"
- Você pode alterar o nome e modificar o campo Color, por exemplo, para branco (#FFFFFF). Clique em Next e Finish a seguir.

Figura 16 Adicionando o ícone na cor branca
Lembre-se que, imagens vetoriais não são exatamente figuras, mas sim definições de formas. Assim, é possível modificar suas propriedades como cor dos elementos e plano de fundo.
Feito isso, estamos prontos para usar o novo ícone na nossa aplicação em diversos tipos de componentes.
Adicionado ícones ao aplicativo Lista de Tarefas
Neste projeto, vamos usar esses ícone para os FABs (Float Action Button), itens de menu e botões do sistema.
- Repita os passos anteriores para adicionar quatro ícones ao projeto Lista de Tarefas: login, add, edit e delete.
Como resultado final vamos ter todos os ícones para a aplicação dentro da pasta drawable dos recursos do projeto.
4. Menus
Menus no Android são componentes de interface de usuário que oferecem opções ou ações para os usuários interagirem com o aplicativo. Eles são amplamente usados para proporcionar acesso rápido a funcionalidades, configurações ou outras áreas da aplicação.
No Android, os menus podem ser de diferentes tipos, dependendo do contexto e do design:
- Options Menu (Menu de Opções)
- Aparece na parte superior da tela (na barra de ferramentas ou como um ícone de "três pontos" se houver muitas opções)
- Geralmente contém ações importantes ou comuns no escopo da atividade ou fragmento.
- Context Menu (Menu de Contexto)
- Aparece quando o usuário realiza uma interação longa (long press) em um item.
- Oferece ações específicas relacionadas ao item selecionado.
- Popup Menu (Menu de Popup)
- Aparece como uma janela flutuante ao lado de um elemento clicado.
- Oferece opções relacionadas a esse elemento em específico.
- Ideal para ações rápidas ou contexto restrito a um botão.
Menus são fundamentais para estruturar a navegação e as interações de um aplicativo Android. Eles ajudam a organizar ações e opções, garantindo que o usuário tenha acesso às funcionalidades importantes de forma intuitiva.
4.1. Elaborando Menus
Os menus no Android geralmente são definidos em arquivos XML localizados no diretório res/menu. Na template Basic Views Activity, já existe um arquivo de menu criado automaticamente. Assim, para o nosso aplicativo, vamos reescrever esse arquivo.
Caso você precise de um novo menu, pode criar facilmente um novo arquivo XML, da mesma forma como criamos uma nova classe no projeto. De fato, cada atividade ou fragmento pode possuir seu menu específico. No caso do aplicativo Lista de Tarefas vamos adicionar um menu ao fragmento da página inicial.
O Android Studio oferece uma interface gráfica que facilita a criação e edição de menus. Ao abrir um arquivo XML do tipo menu, a paleta do designer exibe todas as opções disponíveis para personalizar os itens do menu que você deseja adicionar, tornando o processo mais intuitivo e visual.
Adicionando itens de menu
Para nosso aplicativo vamos precisar de um MenuItem. Você pode adicionar um novo MenuItem ou edite o existem e coloque as propriedades a seguir:
- id: action_logout
- icon: selecione o ícone de login
- title: Logout
- showasAction: ifRoom
Como resultado você deve ver a seguinte pré-visualização:
Propriedade showAsAction
O atributo android:showAsAction é usado em menus no Android para determinar como e onde um item do menu será exibido na interface de usuário. Esse atributo geralmente é especificado nos arquivos XML de menu, dentro de <item> tags, e afeta o comportamento dos itens de menu em relação à barra de ferramentas (toolbar) ou à Action Bar.
Seu comportamento pode ser:
- never
- O item nunca será exibido na Action Bar como um botão ou ícone.
- Ele sempre estará disponível no menu de overflow (os "três pontinhos" no canto superior direito da tela).
- ifRoom
- O item será exibido na Action Bar como um botão ou ícone somente se houver espaço suficiente.
- Caso contrário, será movido para o menu de overflow.
- always
- O item sempre será exibido na Action Bar como um botão ou ícone, independentemente do espaço disponível.
- Nota: Usar always é desencorajado, pois pode levar a uma interface desordenada em dispositivos com telas menores.
- withText
- O item será exibido na Action Bar com o ícone e o texto ao mesmo tempo.
- Isso geralmente é usado em dispositivos maiores e pode ser ignorado em dispositivos com telas pequenas.
- collapseActionView
- Este comportamento é usado com widgets interativos, como barras de busca ou caixas de texto.
- Quando configurado, o item é exibido como um ícone colapsado na Action Bar, mas pode expandir para mostrar uma visualização personalizada quando clicado.
4.2. Programação de Menus
A API do Android evoluiu, e a abordagem tradicional para lidar com menus por meio de métodos como setHasOptionsMenu, onCreateOptionsMenu e onOptionsItemSelected foi marcada como obsoleta.
A forma recomendada agora é usar o MenuProvider, uma interface moderna e mais flexível para gerenciar menus em atividades e fragmentos. O MenuProvider não apenas substitui os métodos anteriores, mas também oferece uma integração mais alinhada com o ciclo de vida do componente, facilitando o gerenciamento e a modularização do código.
Adicionando menus e ações ao fragmento Home
Abra o arquivo HomeFragment.kt e edite o método onViewCreated para incluir o menu criado anteriormente e adicionar uma implementação ao clique no item de Logout.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.menu_main, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.action_logout -> {
logOut()
true
}
else -> false
}
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
O MenuProvider é registrado no MenuHost da atividade ou do fragmento usando o método addMenuProvider. Essa abordagem substitui os métodos tradicionais agora obsoletos, como onCreateOptionsMenu e onOptionsItemSelected.
Durante essa implementação, usamos um objeto anônimo da interface MenuProvider, que contém as definições sobre como o menu será criado e manipulado. Além disso, o menu é vinculado ao ciclo de vida da view do fragmento utilizando o viewLifecycleOwner. Essa prática garante que o menu só estará ativo enquanto a view do fragmento estiver ativa, evitando problemas de interações inválidas após a destruição do fragmento. O parâmetro Lifecycle.State.RESUMED especifica que o menu só estará disponível quando o fragmento ou atividade estiver no estado ativo e visível.
A criação do menu ocorre dentro do método onCreateMenu. Nele, utilizamos o menuInflater para inflar um arquivo XML que define a estrutura do menu, como neste exemplo em que o arquivo R.menu.main_menu é carregado. Esse processo substitui diretamente o antigo método onCreateOptionsMenu, permitindo que o menu seja exibido na interface quando a atividade ou fragmento inicia. O arquivo XML define os itens e suas propriedades, e esse método é responsável por vinculá-los à interface gráfica.
A manipulação de eventos do menu acontece no método onMenuItemSelected. Esse método é chamado sempre que o usuário interage com um item do menu. Ele verifica qual item foi selecionado utilizando o identificador único de cada item, definido no arquivo XML. Com base no item clicado, o código pode executar ações específicas. No aplicativo Lista de Tarefas só existe um menu, portanto, apenas uma ação, a de logout do usuário. Esse método substitui o antigo onOptionsItemSelected e garante uma experiência interativa e responsiva para o usuário.
A adoção do MenuProvider traz várias vantagens. Ele simplifica o ciclo de vida do menu, garantindo que esteja vinculado ao ciclo correto, reduzindo problemas com fragmentos que interagem com menus já destruídos. Além disso, separa a lógica de criação e manipulação, tornando o código mais modular e fácil de manter. Essa abordagem está alinhada com as práticas modernas do Android e oferece maior flexibilidade no desenvolvimento de aplicações.
5. Desenhando as telas do aplicativo
Nesta etapa, expandiremos a tela inicial do aplicativo para incluir uma lista de tarefas. Para isso, adicionaremos uma RecyclerView, um TextInputLayout e um botão de ação flutuante (FAB) para permitir a inclusão de novas tarefas.
Além disso, personalizaremos o layout de cada item da lista de tarefas. Cada tarefa será representada por um CheckBox, que indicará seu status de conclusão, acompanhado de um texto exibindo sua descrição (nome). Também adicionaremos botões para editar e excluir as tarefas, tornando a interface mais interativa e funcional.
5.1. Layout item_tarefa.xml
Arquivo item_tarefa.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/tarefaCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:checked="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tarefaTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="TextView"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="@+id/tarefaCheckBox"
app:layout_constraintEnd_toStartOf="@+id/editImageButton"
app:layout_constraintStart_toEndOf="@+id/tarefaCheckBox"
app:layout_constraintTop_toTopOf="@+id/tarefaCheckBox" />
<ImageButton
android:id="@+id/deleteImageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/baseline_delete_24" />
<ImageButton
android:id="@+id/editImageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toStartOf="@+id/deleteImageButton"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/baseline_edit_24" />
</androidx.constraintlayout.widget.ConstraintLayout>
TO DO
5.2. Layout fragment_home.xml
TODO
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".HomeFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/nomeTarefaTextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Digite uma nova tarefa">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/tarefasRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_tarefa"
/>
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/addFAB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_margin="8dp"
android:clickable="true"
android:src="@drawable/baseline_add_24" />
</FrameLayout>
TODO
6. Implementação das funcionalidades do Firebase
Nas próximas seções, apresentaremos todas as etapas para implementar as funcionalidades da lista de tarefa. Vamos criar uma classe Tarefa e seu repositório para iteragir com o Firebase. E vamos aprender como lidar com listas no Android, especificamente com RecyclerView.
6.1. Classe Tarefa
A classe Tarefa representa a abstração de uma tarefa, contendo duas propriedades principais: nome e condição (indicando se a tarefa está finalizada ou não). Para que os objetos dessa classe possam ser armazenados no Firebase, utilizamos uma data class em Kotlin. Isso permite que os dados sejam automaticamente convertidos para o formato JSON, garantindo a compatibilidade e facilitando o processo de serialização e desserialização.
Código da classe:
package ...
data class Tarefa(var nome: String="", var condicao: Boolean=false) {
var key: String = ""
fun setTarefaId(key: String?) {
this.key = key!!
}
}
Propriedade key
Adicionamos à classe Tarefa um atributo chamado key, que será utilizado como a chave do registro no Firebase. Esse atributo não faz parte das informações armazenadas dentro do objeto Tarefa no banco de dados, por isso foi definido fora do construtor principal.
Essa abordagem é necessária porque, ao interagir com a lista de tarefas e realizar ações específicas (como edição ou exclusão), precisamos identificar com precisão o item selecionado por meio de sua chave única no Firebase.
Para facilitar a atribuição desse valor, implementamos o método auxiliar setTarefaId, que define a chave da tarefa específica, permitindo um gerenciamento eficiente dos registros.
6.2. Classe TarefaRepository
O que é um Repositório?
No desenvolvimento de software, especialmente em aplicações Kotlin e Java, o Padrão de Repositório (Repository Pattern) é uma prática comum para encapsular operações que interagem com o banco de dados ou APIs externas. O repositório atua como uma camada intermediária entre a aplicação e a fonte de dados, fornecendo uma interface consistente e abstraindo os detalhes da implementação.
Classe TarefaRepository
A classe TarefaRepository encapsula as operações que interagem com o banco de dados, permitindo adicionar, ler, atualizar e deletar objetos do tipo Tarefa. Vamos descrever cada uma dessas operações e como elas podem ser implementadas.
Código da classe:
package ...
import android.util.Log
import androidx.lifecycle.MutableLiveData
import com.google.firebase.auth.ktx.auth
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
import com.google.firebase.ktx.Firebase
class TarefaRepository {
//padrão singleton do Kotlin - garante que existe apenas um objeto do tipo no app
companion object {
private fun getCurrentRef(): DatabaseReference {
//acessa a referência a lista de tarefas do usuário logado
val auth = Firebase.auth
val uuid = auth.uid.toString()
return FirebaseDatabase.getInstance().getReference("tarefas").child(uuid).child("lista")
}
fun carregarTarefa(_tarefas: MutableLiveData<MutableList<Tarefa>>) {
//obtem a referencia a lista de tarefas do usuario
val mTarefasRef = getCurrentRef()
//configura a leitura em tempo real das tarefas para atualizar a lista
mTarefasRef!!.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val listaDeTarefas = mutableListOf<Tarefa>()
for (tarefaSnapshot in snapshot.children) {
val tarefa = tarefaSnapshot.getValue(Tarefa::class.java)
tarefa?.setTarefaId(tarefaSnapshot.key)
tarefa?.let { listaDeTarefas.add(it) }
}
_tarefas.value = listaDeTarefas
}
override fun onCancelled(error: DatabaseError) {
Log.e("TODO2024","Erro ao buscar tarefas: ${error.message}")
}
})
}
fun addTarefa(tarefa: Tarefa) {
val mTarefasRef = getCurrentRef()
mTarefasRef.push().setValue(tarefa).addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d("TODO2024","Tarefa criada:" + tarefa.nome)
} else {
Log.w("TODO2024", "Falha ao cadastrar tarefa", task.exception)
}
}
}
fun deleteTarefa(key: String) {
val mTarefasRef = getCurrentRef()
mTarefasRef.child(key).removeValue().addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d("TODO2024","Tarefa deletada:" + key)
} else {
Log.w("TODO2024", "Falha ao deletar tarefa", task.exception)
}
}
}
fun updateCondicao(key: String, bool: Boolean) {
if (key == "")
return
val mTarefasRef = getCurrentRef()
mTarefasRef.child(key).child("condicao"). setValue(bool).addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d("TODO2024","Tarefa atualizada:" + key)
} else {
Log.w("TODO2024", "Falha ao atualizar tarefa", task.exception)
}
}
}
}
}
Companion object
No Kotlin, companion object é um recurso que permite declarar membros estáticos dentro de uma classe, de forma semelhante ao conceito de membros estáticos em Java. Esses membros pertencem à classe em si, e não às instâncias dela. Você pode usar companion object para definir funções, propriedades e constantes que podem ser acessadas diretamente pela classe. Usamos essa estratégia no repositório para ter fácil acesso as métodos dele ao longo do aplicativo, sem a necessidade de instanciar um objeto.
Método getCurrentRef
O método getCurrentRef retorna a referência ao caminho específico no Firebase, considerando o usuário atualmente logado. No banco de dados, as informações das tarefas de cada usuário serão armazenadas em caminhos separados, seguindo o padrão "tarefas/{uuid}/xxx", onde {uuid} é o código único gerado pelo sistema de autenticação do Firebase.
Essa abordagem garante que os dados de cada usuário sejam salvos de forma isolada, evitando conflitos ou alterações indesejadas em informações de outros usuários. Além disso, ela reforça a segurança e a privacidade, já que um usuário não poderá acessar ou modificar os dados de outro, mesmo que ambos utilizem o mesmo aplicativo.
Método carregarTarefa
O método carregarTarefa recebe um objeto do tipo genérico MutableList. No Kotlin, uma MutableList é uma lista que permite adicionar, remover ou alterar elementos. Ao contrário de uma List (que é imutável), uma MutableList proporciona flexibilidade para modificar os dados conforme necessário, sendo especialmente útil ao trabalhar com coleções dinâmicas.
Além disso, utilizamos um LiveData, especificamente um MutableLiveData. Um LiveData é uma classe observável do Android Jetpack projetada para armazenar e gerenciar dados de forma que as alterações possam ser refletidas automaticamente na interface do usuário. Ela é sensível ao ciclo de vida das atividades e fragmentos, garantindo que os observadores sejam notificados apenas quando esses componentes estiverem no estado ativo. Uma das principais vantagens de usar LiveData é que ele permite fazer atualizações automáticas, sempre que o valor do LiveData muda, a UI é atualizada automaticamente.
Neste contexto, utilizamos o addValueEventListener do Firebase para monitorar alterações em tempo real nos dados. A cada alteração, o Firebase retorna um DataSnapshot contendo os dados atualizados. Percorremos esse snapshot e populamos uma lista interna de tarefas. Após processar os dados, atualizamos o valor do MutableLiveData com a lista, notificando automaticamente a interface do usuário para refletir as novas informações. Isso garante que a UI sempre exiba os dados mais recentes do banco de dados.
Método add, update e delete
Os demais métodos do repositório realizam as operações necessárias no banco de dados usando os caminhos especificados. Assim como no cadastro de usuários, utilizamos o método setValue para adicionar ou atualizar objetos no Firebase. Esse método converte o objeto Kotlin para o formato JSON e o armazena no caminho definido.
Para excluir dados, utilizamos o método removeValue. Esse método apaga o valor armazenado no caminho especificado no Firebase. Por exemplo, ao chamar removeValue em uma referência específica, o Firebase exclui o nó correspondente, garantindo que os dados sejam removidos do banco de forma eficiente.
A execução dessas operações é assíncrona, e podemos processar os resultados usando métodos como addOnCompleteListener para verificar o sucesso ou falha da solicitação.
7. Manipulação de Listas
Nesta seção vamos mostrar como manipular lista do Android com Kotlin.
7.1. RecyclerView
O RecyclerView é uma versão aprimorada dos componentes ListView e GridView, que já existiam desde a primeira versão do Android para exibir listas e grades de conteúdo. Ele traz uma série de melhorias significativas em termos de funcionalidade, flexibilidade e desempenho. Suas principais vantagens:
Atualizações Frequentes e Independentes da Versão do Android
Enquanto o ListView e o GridView fazem parte da SDK padrão do Android, recebendo atualizações apenas com novas versões do sistema operacional, o RecyclerView é parte da Biblioteca de Suporte v7 (agora AndroidX). Isso permite que ele seja atualizado frequentemente, independentemente da versão do Android instalada no dispositivo. Assim, melhorias, novas funcionalidades e correções de bugs são disponibilizadas com muito mais frequência.
Melhor Desempenho com Reuso de Views
Como o nome sugere, o RecyclerView utiliza um mecanismo de reciclagem de views. À medida que o usuário desliza a lista para cima ou para baixo, o componente reutiliza as views que saíram da tela, aplicando novos dados conforme a posição correspondente.
Esse processo evita a criação contínua de novas views, economizando memória e melhorando a performance da aplicação. Ele utiliza o padrão de design Object Pool, que reduz o custo de instanciar novos objetos e otimiza o uso de recursos.
Além disso, o padrão ViewHolder é obrigatório no RecyclerView, o que facilita a manutenção de referências de views, evitando a repetida busca por elas com findViewById.
Suporte Nativo a Animações
O RecyclerView vem com a classe DefaultItemAnimator, que gerencia animações para operações como adição, remoção ou movimentação de itens na lista. Isso proporciona um feedback visual elegante e consistente ao usuário.
No ListView e GridView, as animações precisam ser implementadas manualmente pelo desenvolvedor, o que exige mais esforço e complexidade.
Gerenciador de Layout (LayoutManager)
Uma das maiores vantagens do RecyclerView é sua flexibilidade no layout dos itens. Com o uso de diferentes LayoutManagers, você pode facilmente alternar entre layouts verticais, horizontais ou em grade sem reescrever a lógica do componente.
No ListView, a orientação é limitada a listas verticais, e qualquer mudança para outro tipo de disposição requer a utilização de outro componente, como o GridView, resultando em mais código e ajustes.
Atualização Otimizada de Itens
Com o RecyclerView, a API permite a atualização eficiente de itens específicos, sem a necessidade de recarregar toda a lista. Métodos como notifyItemInserted, notifyItemChanged e notifyItemRemoved facilitam a manipulação granular da lista.
Já no ListView, qualquer alteração geralmente exigia o recarregamento completo da lista, ou então um código complexo para atualizar itens individualmente, impactando negativamente a performance.
7.2. ViewHolder e Adapter
Para a implementação de listas vamos precisar de dois novos elementos: ViewHolder e Adapter.
ViewHolder
O padrão ViewHolder é amplamente utilizado no Android para otimizar o desempenho de listas, como as gerenciadas pelo RecyclerView. Ele ajuda a evitar chamadas excessivas ao método findViewById, economizando recursos e melhorando a fluidez da interface.
Adapter
O Adapter é uma ponte entre os dados e os componentes visuais. Ele conecta os dados da sua fonte (por exemplo, uma lista de objetos) ao RecyclerView, responsável por exibir esses dados na tela.
Principais métodos do Adapter:
- onCreateViewHolder
- Cria uma nova instância de um ViewHolder, associando-o a um layout específico (geralmente um item da lista).
- onBindViewHolder
- Vincula os dados a um item específico da lista. Nesse método, você define como cada elemento da lista será exibido.
- getItemCount
- Retorna o número total de itens na lista. Isso informa ao RecyclerView quantos elementos serão exibidos.
7.3. Adaptador da Lista de Tarefa
Nesta seção, vamos apresentar a implementação do adaptador (Adapter) para a lista de tarefas, juntamente com a criação do ViewHolder. O ViewHolder é responsável por otimizar o acesso aos elementos de interface de cada item da lista.
Para isso, utilizaremos o layout do item, que foi criado anteriormente, para desenhar cada item da lista de tarefas. Este layout define a aparência de cada item na RecyclerView, como o nome da tarefa e os botões associados.
Além disso, vamos implementar ações específicas para cada item da lista, como reagir ao clique nos botões de cada tarefa. Essas interações serão tratadas com a ajuda das funções do repositório, que são responsáveis pelas operações no banco de dados, como atualizar ou excluir tarefas.
Com essa implementação, a lista de tarefas será não apenas exibida, mas também funcional, permitindo ao usuário interagir de forma intuitiva com as tarefas diretamente na interface.
Código:
package ...
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.ImageButton
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class ListaTarefaAdapter(private val tarefas: MutableList<Tarefa>): RecyclerView.Adapter<ListaTarefaAdapter.TarefaViewHolder>() {
class TarefaViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
//elementos da UI
val nomeTextView = itemView.findViewById<TextView>(R.id.tarefaTextView)
val condicaoCheckBox = itemView.findViewById<CheckBox>(R.id.tarefaCheckBox)
val editButton = itemView.findViewById<ImageButton>(R.id.editImageButton)
val deleteButton = itemView.findViewById<ImageButton>(R.id.deleteImageButton)
//chave do firebase para o item
var key: String = ""
init {
condicaoCheckBox.setOnCheckedChangeListener { compoundButton, bool ->
Log.d("TODO2024","mudou estado da chave: " + key)
TarefaRepository.updateCondicao(key, bool)
}
editButton.setOnClickListener{
//TODO
}
deleteButton.setOnClickListener{
TarefaRepository.deleteTarefa(key)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TarefaViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_tarefa, parent, false)
return TarefaViewHolder(view)
}
override fun onBindViewHolder(holder: TarefaViewHolder, position: Int) {
val tarefa = tarefas[position]
holder.nomeTextView.text = tarefa.nome
holder.condicaoCheckBox.isChecked = tarefa.condicao
//assign key
holder.key = tarefa.key
}
override fun getItemCount(): Int {
return tarefas.size
}
// Método para atualizar a lista de tarefas
fun updateData(newTarefas: List<Tarefa>) {
tarefas.clear()
tarefas.addAll(newTarefas)
notifyDataSetChanged() // Notifica que os dados foram alterados
}
}
8. Lógica da Aplicação com ViewModel
Neste capítulo mostraremos a implementação do fragmento principal e sua viewmodel.
8.1. ViewModel e vinculação de dados
O que é uma ViewModel?
O papel de uma ViewModel, é prover dados para a interface gráfica do usuário e sobreviver a mudanças nas configuração (Ciclo de Vida). Uma ViewModel atua como uma comunicação entre o repositório e a interface gráfica (atividade/fragmento). Você também pode usar uma ViewModel para compartilhas dados entre fragmentos diferentes. A classe ViewModel é um dos componentes da biblioteca LifeCycle do Android Jetpack.

Para uma introdução mas completa acessa a documentação.
Porque usar uma ViewModel?
Uma ViewModel guarda os dados de um aplicativo e observa os seus ciclos de vida de forma para garantir que eles sobrevivem a mudanças nas configurações. Uma simples mudança como girar a tela do dispositivo faz com que uma atividade/fragmento seja reconstruído o que pode acarretar perda das informações. ViewModel resolvem esse problema pois separa os dados da Activity/Fragment.
Separar os dados da interface gráfica é uma maneira elegante de seguir o princípio da responsabilidade única: a atividade e o fragmento fica responsáveis em cuidar da apresentação dos dados na tela, enquanto a ViewModel se encarrega de armazenar e processar os dados necessários na interface gráfica.
Em uma ViewModel, se usa a classe LiveData para armazenar e observar os valores para serem usados ou mostrados pela interface gráfica. Usar LiveData trás alguns benefícios:
- Você pode observar os dados e configurar para que mudanças sejam automaticamente processadas e mostradas na interface gráfica
- O repositório e a interface gráfica estão separados da ViewModel. Não existem chamadas diretas ao banco de dados pela ViewModel, o que facilita a criação de testes unitários.
Arquitetura recomendada

8.2. HomeViewModel
package ...
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class HomeViewModel : ViewModel() {
// LiveData para armazenar a lista de tarefas
private var _tarefas: MutableLiveData<MutableList<Tarefa>> = MutableLiveData<MutableList<Tarefa>>()
val listaTarefas: LiveData<MutableList<Tarefa>> get() = _tarefas
init {
carregarTarefas()
}
private fun carregarTarefas() {
//solicita ao repositorio para povoar a lista de tarefas
TarefaRepository.carregarTarefa(_tarefas)
}
fun addTarefa(descricao: String, condicao: Boolean) {
val tarefa = Tarefa(descricao, condicao)
TarefaRepository.addTarefa(tarefa)
}
}
8.3. HomeFragment
package ...
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.view.MenuHost
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.navigation.Navigation.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import br.edu.ifsc.fulano.minhastarefas.databinding.FragmentHomeBinding
import com.google.android.material.snackbar.Snackbar
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
class HomeFragment : Fragment() {
//binding das views automatizado
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
//firebase
private lateinit var mAuth: FirebaseAuth //acessa os recursos de autenticação do Firebase
companion object {
fun newInstance() = HomeFragment()
}
private val viewModel: HomeViewModel by viewModels()
//adaptador da recyclerview
private var listaTarefaAdapter: ListaTarefaAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// TODO: Use the ViewModel
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
//configura a vinculação
_binding = FragmentHomeBinding.inflate(inflater, container, false)
//Configura a recyclerView
val recyclerView = binding.tarefasRecyclerView
recyclerView.layoutManager = LinearLayoutManager(context)
//inicializa o adaptar com uma lista vazia
listaTarefaAdapter = ListaTarefaAdapter(ArrayList<Tarefa>())
recyclerView.adapter = listaTarefaAdapter
//Configura a ação do FAB
binding.addFAB.setOnClickListener{
addTarefa()
}
return binding.root
}
override fun onStart() {
super.onStart()
//inicializa o serviço de autenticação do Firebase
mAuth = Firebase.auth
//obtem os dados do usuário atual
val currentUser = mAuth.currentUser
//verifica se o usuário existe
if (currentUser != null) {
view?.let{
Snackbar.make(it, "Usuário logado: " + currentUser.email, Snackbar.LENGTH_LONG).show()
}
getTarefas()
}
//caso contrário encaminha para a página de login
else {
Log.d("TODO2024", "Usuário deslogado")
findNavController(requireView()).navigate(R.id.action_homeFragment_to_loginFragment)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.menu_main, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.action_logout -> {
logOut()
true
}
else -> false
}
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
fun logOut(){
//faz logout e vai para a página de login
mAuth.signOut()
view?.let{
Snackbar.make(it, "Usuário deslogado.", Snackbar.LENGTH_LONG).show()
}
findNavController(requireView()).navigate(R.id.action_homeFragment_to_loginFragment)
}
fun getTarefas() {
view?.let{
Snackbar.make(it, "Lendo tarefas...", Snackbar.LENGTH_LONG).show()
}
//usa a LiveData da viewModel para escutar mudanças
//ao ouvir mudanças atualiza a lista de tarefas do adaptador
viewModel.listaTarefas.observe(this) { tarefas ->
listaTarefaAdapter?.updateData(tarefas)
}
}
fun addTarefa() {
//faz a leitura dos dados
val descricao = binding.nomeTarefaTextInputLayout.editText?.text.toString().trim()
//se o campo estiver em branco encerra a operação
if (descricao == "")
return
//envia os dados a ViewModel para adicionar uma nova tarefa
viewModel.addTarefa(descricao, false)
//limpa os campos
binding.nomeTarefaTextInputLayout.editText?.text?.clear()
}
}
8.4. MainActivity
package br.edu.ifsc.fulano.minhastarefas
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import android.view.Menu
import android.view.MenuItem
import br.edu.ifsc.fulano.minhastarefas.databinding.ActivityMainBinding
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
val navController = findNavController(R.id.nav_host_fragment_content_main)
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)
}
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment_content_main)
return navController.navigateUp(appBarConfiguration)
|| super.onSupportNavigateUp()
}
}
9. Considerações Finais
Neste livro, você desenvolveu a primeira parte do aplicativo Lista de Tarefas, discutindo as funcionalidades principais e a integração com os serviços do Firebase.
Exploramos a arquitetura de um aplicativo Android mais complexo, utilizando uma toolbar, navegação e fragmentos para organizar as telas. Com a "Basic Views Activity" como ponto de partida, você entendeu como essa template preconfigura uma base sólida, com fragmentos e navegação integrados para facilitar o desenvolvimento.
A interface gráfica (GUI) foi construída com diferentes tipos de layout, e você aprendeu a utilizar componentes como o TextInputLayout, personalizando formulários por meio das propriedades `imeOptions` e `inputType` para otimizar a entrada de dados.
No Firebase, configuramos os serviços de autenticação e banco de dados em tempo real (Realtime Database), além de implementar um sistema de usuários com telas de login, cadastro e página inicial.
Também aprendemos a salvar dados no Realtime Database, onde cada usuário tem seu documento único.
Na segunda parte do projeto, vamos expandir o aplicativo para incluir funcionalidades da lista de tarefas, exibindo na tela inicial uma lista com as tarefas, um campo de entrada para adicionar novas, além de botões para excluir e checkboxes para marcar tarefas concluídas.
10. Referências
Paul Deitel, Harvey Deitel, Abbey Deitel, Michael Morgano. ANDROID para programadores: uma abordagem baseada em aplicativos. Revisão de Daniel Antonio Callegari; Tradução de João Eduardo Nobrega Tortello. Porto Alegre: Bookman, 2012.
LECHETA, Ricardo R. Android essencial com Kotlin. São Paulo: Novatec, 2019.
Documentação do Firebase para Android Development. Android Developers, Google. Disponível em: https://firebase.google.com/docs/android/setup. Acesso em: 22 out. 2024.
11. Ficha Técnica
| VERSÃO ATUAL - 2024.2 | |
|---|---|
| Autoria | Bruno Crestani Calegaro |
| Design Instrucional | Bruno Crestani Calegaro |
| Design Multimídia | Bruno Crestani Calegaro |
| Revisão textual e normativa |
Bruno Crestani Calegaro |

Este trabalho está licenciado com uma Licença Creative Commons - Atribuição-NãoComercial-CompartilhaIgual 4.0 Internacional.