Armazenando dados no Firebase com Android — Realtime Database

Quem falou que aplicações simples precisam de um super back-end?

E chegamos ao último post da série sobre Database! Estamos felizes pelo conteúdo compartilhado e um pouco tristes por já estarmos acabando… Se você chegou agora e quer ler os dois primeiros posts da série é só clicar nos links abaixo, relacionados aos outros dois tipos de armazenamentos existentes hoje em dia no Firebase:

Cloud Firestore

Storage

Nos primeiros posts desta série, nós introduzimos um conteúdo prévio sobre como o dado seria armazenado. Não diferente deles, o post de hoje também precisa de uma introdução à estrutura que vai ser utilizada para armazenar esses dados.

Assim como os demais, aqui eu também consigo criar regras de armazenamento. É possível inclusive criar backups (até o momento esta opção é exclusiva para o plano Blaze) e também relatórios de uso.

https://console.firebase.google.com/
https://console.firebase.google.com/

Na imagem acima é possível ver que temos algumas poucas informações sobre conexões simuntâneas, quantidade de armazenamento, a quantidade de informação que foi efetuada de download e, por fim, a porcentagem do banco que está ocupada.

Por default, as regras desabilitam o acesso à leitura e escrita na sua base de dados. Para facilitar, no nosso exemplo vamos criar uma regra para que qualquer usuário possa ler e escrever em nosso banco de dados, mas lembrando que isso NÃO é uma prática que deve ser utilizada em projetos reais.

// Esta regra IMPOSSIBILITA PARA QUALQUER USUÁRIO a leitura e escrita 
// na nossa base de dados
{
"rules": {
".read": false,
".write": false
}
}
// Esta regra POSSIBILITA PARA QUALQUER USUÁRIO a leitura e escrita
// na nossa base de dados
{
"rules": {
".read": true,
".write": true
}
}

Para saber mais sobre regras de autenticação no Firebase, clique aqui.

Assim como o Cloud Firestore tem suas peculiaridades na hora de armazenar os dados, temos também alguns detalhes na hora de armazenar os dados no Realtime Database.

Para a nossa aplicação, vamos desenvolver um canal de posts entre usuários. Vejamos uma imagem da estrutura dos dados e, em seguida, a explicação do que está acontecendo:

https://console.firebase.google.com/

Na imagem acima temos em primeira ordem um nó raiz, entitularizado como posts, que vai representar a estrutura de todos os posts compartilhados entre os usuários. Dentro de posts, temos uma estrutura de nó que relaciona um id para uma postagem específica e posteriormente os campos deste post.

Perceba que os campos são nós que contêm valor, e os nós que contêm filhos não contêm valor. É possível ver que no último elemento que seria um campo e não está preenchido tem um símbolo “+”, que seria a possibilidade de adicionar um nó filho. Porém, no momento em que adicionamos o valor do campo esse símbolo desaparece, impossibilitando que aquele nó contenha um nó filho, e se adicionamos um nó filho, o campo valor desaparece por consequência.

Esta imagem e esta estrutura que vão ser utilizadas não representam a melhor estrutura de dados possível de ser criada. É preciso um estudo mais profundo sobre banco de dados não relacional para poder definir qual seria a melhor estrutura em posts que necessitam uma robustez muito grande, como os mais utilizados hoje em dia.

Antes de começar a codar loucamente…

Temos que entender como funciona um banco de dados que responde em real time. O que significa esse real time? Como a atualização é feita?

No caso desse tipo de base de dados, as requisições não são feitas como tipicamente estamos acostumados (o comum HTTP ou HTTPS). O Firebase utiliza um listener para que, quando os dados sejam modificados, nós possamos receber as atualizações quase que instantaneamente, em qualquer device que esteja conectado.

Além disso, o SDK do Firebase nos permite utilizar tudo off-line e quando nos conectarmos com o serviço tudo passa a ser atualizado com o banco externo, seja por meio do Android, que é o caso que veremos aqui, ou em um navegador ou outro dispositivo que esteja conectado ao nosso serviço.

Adicione a biblioteca específica do Firebase:

implementation 'com.google.firebase:firebase-database:19.2.0'

Já existem as libs “-ktx”, mas o projeto discutido nesse post não fazia uso delas.

Utilizando o mesmo padrão de projeto do artigo de Auth para a injeção de dependências, temos o módulo definido abaixo:

val networkModule = module {
...
single{
FirebaseDatabase.getInstance()
}
...
}

Obs: importante lembrar que devemos inicializar o FirebaseApp no aplicativo para que não ocorra nenhum erro ao tentar utilizar as funções da instância do Firebase no seu projeto. Para isso utilize, de preferência na sua classe de application, o seguinte trecho de código:

FirebaseApp.initializeApp(context)

Bem, dada uma introdução inicial sobre como os dados estão armazenados, a configuração inicial já realizada e como devemos pensar na hora de consumir esses dados, vejamos alguns exemplos:

class ServicePost(private val realtimeDatabase: FirebaseDatabase, private val auth: FirebaseAuth): ServicePostContract{

private val POSTS_REF = "posts"

override fun
getReference(): DatabaseReference = realtimeDatabase.getReference(POSTS_REF)

override fun insertPost(post: Post) {
...
}

override fun deletePost(post: Post) {
...
}
}

Entenda essa classe como um service que está em uma pasta de requests ou repository. Como em seguida nós temos que definir um listener atrelado a essa referência de post, se preferir use esse nome para estruturar a arquitetura do nosso projeto. Acima temos a função getReference() e essa função foi definida para que a responsabilidade de retornar a referência seja somente atrelada ao service.

class PostViewModel(private val servicePostContract: ServicePostContract): ViewModel(),
KoinComponent {

val post = MutableLiveData<BaseModel<List<Post>, Throwable>>()

fun setUpListener(){
val postListener: ValueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val postList = ArrayList<Post>()
for (postSnapshot in dataSnapshot.children) {
val newPost = postSnapshot.getValue(Post::class.java)
newPost?.let{
postList.add(it)
}
}
post.value = BaseModel(BaseModel.Status.SUCCESS, postList, null)
}

override fun onCancelled(databaseError: DatabaseError) {
post.value = BaseModel(BaseModel.Status.ERROR, null, Throwable("We try to load a new Post but it failed!"))
Log.w("Load Post: ", "loadPost:onCancelled", databaseError.toException())
}
}
servicePostContract.getReference().addValueEventListener(postListener)
}
}

Acima, no nosso viewModel temos a definição de uma única função que nós precisamos para definir quem é o nosso Listener. Dentro do escopo dessa função, temos a declaração de uma variável chamada postListener do tipo ValueEventListener e para utilizar esse tipo é necessário implementar duas funções que são responsáveis por nos informar quando algum dado do repositório é modificado e quando a solicitação não é concluída. A principal delas é o onDataChange, que informa imediatamente quando algum dado nosso é modificado no Realtime Database do Firebase.

Depois de definirmos o que essa função deve fazer, no nosso exemplo específico aqui seria a conversão do objeto e o post em um MutableLiveData para informar a view que temos dados novos e, assim, ela se resolver para atualizar, só é necessário dizer que a referência que recebemos do service tem o listener que nós determinamos e está tudo pronto! Com isso, toda vez que algum dado for modificado, adicionado ou deletado, nossa aplicação vai saber e atualizar automaticamente.

override fun insertPost(post: Post) {
val key: String? = getReference().push().getKey()
post.id = key
val postValues = post.toMap()
val childUpdates: MutableMap<String, Any> = HashMap()
childUpdates["/$POSTS_REF/$key"] = postValues
realtimeDatabase.getReference().updateChildren(childUpdates)
.addOnSuccessListener{}.addOnFailureListener{}
}

Completando nossa classe service, temos a função acima que é responsável por inserir um post novo por um determinado usuário. Criamos uma chave nova para inserir um novo post com uma chave única, em seguida transformamos o objeto em um HashMap para que tenha o mesmo formato do repositório e, por fim, atualizamos especificamente os campos da chave que tínhamos criado. Caso queira acompanhar a solicitação, defina as funções addOnSuccessListener{} e addOnFailureListener{}.

override fun deletePost(post: Post) {
getReference().child(post.id ?: "").removeValue()
.addOnFailureListener { }
.addOnSuccessListener { }
}

Simplemente definimos o elemento que queremos deletar e executamos a função removeValue().

Bem, agora que consolidamos o que acontece de fato na atualização, vou mostrar um “truque” para a criação de um RecyclerView.Adapter de maneira fácil e que vai substituir o nosso Listener.

Imagine que, em nossa view, ao invés de criar um adapter próprio para que você possa fazer a atualização das mensagens, você possa utilizar um que já está pronto. Assim, você precisa modificar somente o ViewHolder e as Funções da RecyclerView.

Para utilizar o que vai ser mostrado abaixo é necessário adicionar a seguinte biblioteca:

implementation "com.firebaseui:firebase-ui-database:6.2.1"

No view model, adicione uma função que resolve a referência e cria um FirebaseRecyclerOptions.

fun getOptions(){  
val
options = FirebaseRecyclerOptions
.Builder<Post()
.setQuery(servicePostContract.getReference(),Post::class.java)
.build()
}

Em seguida, temos a função setUpAdapter, que define um adapter do Firebase para a nossa RecyclerView e que mostra os posts.

private fun setUpAdapter(view: View) {
postAdapter = object : FirebaseRecyclerAdapter<Post, PostViewHolder>(postViewModel.getOptions()) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_post, parent, false)
return PostViewHolder(view)
}

override fun onBindViewHolder(holder: PostViewHolder, position: Int, model: Post) {
holder.title.text = model.title
holder.contentView.text = model.body
holder.author.text = model.author
}
}

if (view is RecyclerView) {
with(view) {
layoutManager = LinearLayoutManager(context)
adapter = postAdapter
}
}
}

Veja que é exatamente do mesmo jeito que naturalmente definimos um Adapter. O detalhe é que após isso é só necessário adicionar o seguinte:

override fun onStart() {
super.onStart()
postAdapter?.startListening()
}

override fun onStop() {
super.onStop()
postAdapter?.stopListening()
}

Assim, você garante que vai estar ouvindo quando precisa e desligar quando é necessário para que sua aplicação não consuma desnecessariamente um processamento em um momento inadequado.

Um grande ponto que pode ser adicionado ao FirebaseRecyclerAdapter é que existem as funções onDataChanged e onError, que nos permitem modificar inclusive o modo que o dado é transformado.

Alguns vão dizer que é magia, outros vão pensar que o Firebase é muito bom. Além desses recursos, existe uma infinidade de outros tão bons quanto. Como falado no começo desta série, o Firebase não substitui um back-end complexo, mas muita coisa é resolvida com ele e com a versão gratuita. Espero que tenham gostado! O que foi apresentado aqui nesta série é só o comecinho do que o Firebase pode nos proporcionar. Pretendo buscar mais conhecimento e, assim que adquirido, trago novos posts para poder compartilhar com vocês.

Se você quer fazer parte de um time que está compartilhando conhecimento e aprendendo junto o tempo todo, é só dar uma olhada aqui e se candidatar a uma de nossas vagas. Vamos aprender juntos!

Até mais! :D

Concrete

Nós desenvolvemos produtos digitais com inovação, agilidade…

Concrete

Nós desenvolvemos produtos digitais com inovação, agilidade e excelentes práticas, para que o mercado brasileiro e latino-americano acompanhe a velocidade do mercado digital mundial.

Daivid Vasconcelos Leal

Written by

Currently Master Student at UFRPE in Applied Computer Science with the field Quantum Computer, and Android Developer at Concrete Solutions.

Concrete

Nós desenvolvemos produtos digitais com inovação, agilidade e excelentes práticas, para que o mercado brasileiro e latino-americano acompanhe a velocidade do mercado digital mundial.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store