Firebase App - Parte 1

8. Programando Login com Firebase

8.4. Classe CadastroFragment

O fragmento de tela de Cadastro é responsável por registrar um novo usuário no sistema, realizando duas tarefas principais: criar uma conta no serviço de autenticação do Firebase com o e-mail e senha fornecidos e salvar os dados do usuário no documento "usuarios" do Realtime Database.

Para a implementação, utilizaremos o recurso de View Binding, assim como os objetos da biblioteca Firebase: FirebaseAuth para autenticação, FirebaseDatabase para o banco de dados e DatabaseReference para a referência ao documento onde os dados serão armazenados.

Além disso, continuaremos a usar a programação assíncrona para as operações com serviços externos ao app, garantindo que as interações com o Firebase sejam tratadas de forma eficiente e sem interrupções na interface.

Como funciona o Realtime Database do Firebase?

Antes de continuar primeiramente devemos entender como funciona a estrutura do banco de dados NoSQL no Firebase. Para tanto, é preciso entender que não existem tabelas em um sistema NoSQL e sim documentos. Por causa disso, é possível rapidamente iniciar o desenvolvimento de um aplicativo, pois não é necessário a construção de todo um esquema de banco de dados e seus relacionamentos. O Firebase oferece liberdade ao desenvolvedor como salvar os dados, ficando a cargo da programação como organizar as informações.

No Firebase cada documento é um caminho e neste caminho podem ser salvos dados ou listas. É necessário obter uma referência ao nome do caminho para se ter acesso aos dados e a seguir realizar as operações adequadas seja o dado uma lista ou um objeto. 

Abaixo, o código completo do fragmento de Cadastro e na sequência as explicações sobre cada método:

package ...

import ...

class CadastroFragment : Fragment() {
//binding das views automatizado
private var _binding: FragmentCadastroBinding? = null
private val binding get() = _binding!!
//firebase
private lateinit var mAuth: FirebaseAuth //acessa os recursos de autenticação do Firebase
private lateinit var mDatabase: FirebaseDatabase
private lateinit var mUsuarioRef: DatabaseReference

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentCadastroBinding.inflate(inflater, container, false)


binding.cadastrarButton.setOnClickListener {
cadastrarUsuario();
}

//inicializa firebase
mAuth = Firebase.auth
mDatabase = Firebase.database
mUsuarioRef = mDatabase.getReference("usuarios")

// Inflate the layout for this fragment
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

fun cadastrarUsuario(){
//leitura dos dados inseridos na UI
val nome = binding.nomeTextInputLayout.editText?.text.toString()
val telefone = binding.telefoneTextInputLayout.editText?.text.toString()
val email = binding.emailTextInputLayout.editText?.text.toString()
val senha = binding.senhaTextInputLayout.editText?.text.toString()
Log.d("TODO2024", "Tentando criar usuario auth")
//cria um novo login firebase
mAuth.createUserWithEmailAndPassword(email, senha)
.addOnCompleteListener { task: Task<AuthResult> ->
if (!task.isSuccessful) {
Log.w("TODO2024", "Falha ao criar novo Login: ", task.exception)
} else {
Log.d("TODO2024", "Login criado com sucesso")
//Le o UUI do novo usuário criado
val uui = task.result.user!!.uid
//E cria um novo usuario
val usuario: Usuario = Usuario(nome, telefone, email)
criarUsuario(uui, usuario)
}
}
}

private fun criarUsuario(uui: String, usuario: Usuario) {
mUsuarioRef.child(uui).setValue(usuario)
.addOnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w(
"TODO2024",
"Falha ao criar dados do usuario: ",
task.exception
)
} else {
Log.d("TODO2024", "Usuario criado com sucesso")
goToHome()
}
}
}

private fun goToHome() {
findNavController(requireView()).navigate(R.id.action_cadastroFragment_to_homeFragment)
}
}
Método onCreateView

No método onCreateView, configuramos o View Binding logo na primeira linha. Após o carregamento do arquivo FragmentCadastroBinding, o objeto binding passa a conter todas as referências aos componentes da interface definidos no layout XML, o que facilita a leitura e manipulação dos dados do formulário. O método então retorna o componente raiz da interface para compor a visualização do fragmento. Com binding, acessamos o botão de cadastro e associamos a ele um evento que chamará a função cadastrarUsuario ao ser pressionado.

Também inicializamos os objetos necessários para as operações com o Firebase. O objeto mAuth lida com os serviços de autenticação do app, enquanto mDatabase se conecta ao Realtime Database do projeto. Lembre-se de que o arquivo de configuração google-services.json centraliza as informações para essa conexão, o qual foi adicionado usando o Firebase Assistant do Android Studio — caso ainda não tenha feito essa configuração, é importante revisar os passos anteriores.

Para acessar os dados no Realtime Database a partir de mDatabase, criamos um objeto do tipo DatabaseReference. Essa referência possibilita a leitura e gravação de dados no caminho especificado no banco. Neste caso, queremos acesso ao documento "usuarios", então utilizamos getReference("usuarios"). Para acessar outros documentos, basta ajustar o nome do caminho de referência conforme necessário.

Método cadastrarUsuario

Vamos adicionar um novo usuário ao sistema através do serviço de autenticação do Firebase utilizando e-mail e senha com o método createUserWithEmailAndPassword do objeto mAuth. Esse método faz uma requisição à API do Firebase, e a resposta não é imediata; por isso, precisamos usar programação assíncrona para não bloquear a interface do usuário durante a autenticação.

Novamente precisamos lidar com a programação assíncrona. Vamos implementar um método para lidar com a resposta do evento addOnCompleteListener. Esse evento recebe um objeto task que contém o resultado da autenticação (AuthResult). Com ele, verificamos se a operação foi bem-sucedida usando um fluxo de decisão, que nos permite escolher a ação adequada para cada situação.

  • Em caso de sucesso: login criado com sucesso mas também queremos salvar as informações pessoais do usuário no documento usuarios. Assim, criamos um objeto da classe Usuário com as informações do formulário e chamamos uma função criarUsuario encaminhando o objeto usuário criado e o código UUID. Onde UUID (Universally Unique Identifier) é um identificador único que permite diferenciar cada registro ou usuário no banco de dados.
  • Em caso de falha: registramos uma mensagem de log com o erro. Essas mensagens podem ser visualizadas no LogCat do Android Studio, uma ferramenta útil para diagnosticar problemas. Se o Firebase estiver mal configurado, a conexão de rede apresentar problemas ou houver algum outro erro, o log exibirá informações úteis para corrigir a questão.

Método criarUsuario

Vamos armazenar as informações pessoais do usuário em um documento do Firebase, salvando-o em uma chave cujo valor é o UUID do usuário. Em bancos de dados NoSQL, como o Firebase, não existe uma "chave primária" no modelo tradicional de bancos relacionais, mas o uso de um UUID exclusivo para cada usuário nos permite criar uma estrutura similar. Assim, as informações do usuário serão salvas no caminho "usuarios/uuid", onde cada uuid representa um usuário único no sistema.

Dessa forma, ao salvar diversos usuários na coleção "usuarios", o caminho ficará organizado por UUIDs, e cada documento corresponderá a um usuário específico. Isso facilita futuras consultas e operações de leitura, pois, para obter as informações de um usuário específico, basta acessar diretamente o caminho "usuarios/uuid", onde uuid é obtido a partir do serviço de autenticação do Firebase. Essa estrutura torna as buscas e atualizações mais eficientes, além de manter o banco de dados organizado e escalável.

A primeira linha de código do método criarUsuario pode ser explicada em partes: primeiro, mUsuarioRef referencia o caminho principal "usuarios", enquanto o método child acessa um subcaminho específico — neste caso, o UUID do usuário. Se o subcaminho não existir, ele é automaticamente criado. Em seguida, o método setValue é responsável por salvar o objeto no banco de dados Firebase, convertendo-o para o formato JSON antes de armazená-lo.

Como Usuario é uma data class do Kotlin, seus dados são corretamente convertidos para JSON. Além de objetos complexos, setValue também permite o salvamento de dados simples, como Boolean, Long, Double, String, Map<String, Object> e List<Object>.

Novamente precisamos lidar com programação assíncrona. Em caso de sucesso usamos o método goToHome para a navegação voltar a tela inicial do aplicativo.