Eventos de Leitura e Firebase UI Android - Parte 2
(5286) (17)
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
CategoriaEngenharia de Software
Autor(es)Kent Beck
EditoraNovatec
Edição1ª
Ano2024
Páginas112
Opa, blz?
Nesse vídeo dois da série Firebase no Android são apresentados os cinco eventos de leitura e o uso do Firebase UI para inserção de dados no RecyclerView de usuários do chat.
É apresentado também a vantagem que temos na atualização dos dados na APP quando a entidade ativa da comunicação é o servidor e não a APP. Com o Firebase não há necessidade da utilização de serviços de push message quando a APP está sendo utilizada. O Firebase se encarrega de informar (via mensagem no modelo broadcast) todos os devices que estão utilizando a APP.
Fique atento quanto aos listeners Value e Child, pois apesar do uso do ValueEventListener ser mais simples ele é também ineficiente.
No decorrer do post seguimos com o código do primeiro vídeo. O primeiro passo é atualizar o build.gradle (Module: app). A minTargetSDK é 16 agora, atualizamos a versão do compile do Firebase e inserimos o compile do Firebase UI:
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
applicationId "br.com.thiengo.thiengocalopsitafbexample"
minSdkVersion 16
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
repositories {
mavenCentral()
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.2.1'
compile 'com.android.support:design:23.2.1'
compile 'com.firebase:firebase-client-android:2.5.2+'
compile 'com.firebaseui:firebase-ui:0.3.1'
}
Nossa próxima atualização é no código da classe User.class, primeiro no método saveDB, onde depois da remoção das chamadas a setId() e setPassword() ficamos com o seguinte código:
...
public void saveDB(){
Firebase firebase = LibraryClass.getFirebase();
firebase = firebase.child("users").child( getId() );
firebase.setValue(this);
}
...
Logo acima da assinatura da classe definimos um annotation para ignorar os campos id e password na leitura e inserção de dados no Firebase utilizando um objeto (ou modelo de classe) do tipo User:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
...
@JsonIgnoreProperties({"id", "password"})
public class User {
...
}
Nosso próximo passo é atualizar o layout de nossa MainActivity.class, activity_main.xml. Foi removido o TextView e Button e adicionado um RecyclerView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="br.com.thiengo.thiengocalopsitafbexample.MainActivity">
<android.support.v7.widget.RecyclerView
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:id="@+id/rv_users"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
Agora seguimos com a atualização da classe MainActivity.class. Primeiro removemos o antigo método de logoff e adicionamos os métodos onCreateOptionsMenu() e onOptionsItemSelected():
...
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == R.id.action_update){
startActivity(new Intent(this, UpdateActivity.class));
}
else if(id == R.id.action_logout){
firebase.unauth();
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
finish();
}
return super.onOptionsItemSelected(item);
}
...
Note que nossa lógica de logoff está no segundo condicional do método onOptionsItemSelected(), sem a referencia a nossa LibraryClass, pois firebase agora é uma variável de instância.
Uma pausa para colocarmos o menu.xml no folder /res/menu:
<?xml version="1.0" encoding="utf-8"?>
<menu 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"
tools:context=".MainActivity">
<item
android:id="@+id/action_update"
android:orderInCategory="100"
android:title="@string/atualizar"
app:showAsAction="never" />
<item
android:id="@+id/action_logout"
android:orderInCategory="100"
android:title="@string/logout"
app:showAsAction="never" />
</menu>
Agora voltando a MainActivity devemos definir firebase como variável de instância e implementar o novo código de onCreate():
...
private Firebase firebase;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
firebase = LibraryClass.getFirebase().child("users");
}
...
Agora implementamos um método init() que será responsável por inicializar nosso RecyclerView. Esse método será chamado no onResume():
...
@Override
protected void onResume() {
super.onResume();
init();
}
private void init(){
RecyclerView rvUsers = (RecyclerView) findViewById(R.id.rv_users);
rvUsers.setHasFixedSize( true );
rvUsers.setLayoutManager( new LinearLayoutManager(this));
}
...
Note que ainda não colocamos o adapter no RecyclerView. Essa parte será implementada posteriormente no post.
Na raiz do projeto, onde se encontram as Activities crie um novo package com o nome "listener". Agora criamos a primeira classe listener de teste. Antes é necessário informar que o FIrebase tem cinco eventos de leitura, vamos criar duas novas classes que juntas implementam esses cinco listeners de leitura. A classe CustomValueEventListener implementa o evento de leitura Value e a classe CustomChildEventListener implementa os outros quatro eventos de leitura, são eles: onChildAdded(), onChildChanged(), onChildRemoved() e onChildMoved(). Abaixo a implementação de CustomValueEventListener:
import android.util.Log;
import com.firebase.client.DataSnapshot;
import com.firebase.client.FirebaseError;
import com.firebase.client.ValueEventListener;
import br.com.thiengo.thiengocalopsitafbexample.domain.User;
public class CustomValueEventListener implements ValueEventListener {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
for( DataSnapshot d : dataSnapshot.getChildren() ){
User u = d.getValue( User.class );
Log.i("log", "Name: "+u.getName());
Log.i("log", "Email: "+u.getEmail());
}
}
@Override
public void onCancelled(FirebaseError firebaseError) {}
}
Nosso objetivo com as classes de que implementam as interfaces de listeners, além de entender delas, é descobrir se alguma será útil para o fácil preenchimento de nosso RecyclerView. O método onDataChange() será chamado sempre que houver alguma atualização, inserção ou remoção no nodo do Firebase que estiver sendo utilizado. O método onDataChange() será também chamado na primeira execução do método que vincula essa classe listener ao Firebase. Essa primeira chamada também é verdade para o método onChildAdded() da classe que implementa a interface ChildEventListener.
Abaixo a imagem do nodo "users" que vamos utilizar como conteúdo em nosso código exemplo:
Ainda temos de implementar a CustomChildEventListener que tem os outros métodos listeners:
import android.util.Log;
import com.firebase.client.ChildEventListener;
import com.firebase.client.DataSnapshot;
import com.firebase.client.FirebaseError;
import br.com.thiengo.thiengocalopsitafbexample.domain.User;
public class CustomChildEventListener implements ChildEventListener {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
User u = dataSnapshot.getValue( User.class );
Log.i("log", "ADDED");
Log.i("log", "Name: "+u.getName());
Log.i("log", "Email: "+u.getEmail());
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
User u = dataSnapshot.getValue( User.class );
Log.i("log", "CHANGED");
Log.i("log", "Name: "+u.getName());
Log.i("log", "Email: "+u.getEmail());
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
User u = dataSnapshot.getValue( User.class );
Log.i("log", "REMOVED");
Log.i("log", "Name: "+u.getName());
Log.i("log", "Email: "+u.getEmail());
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {}
@Override
public void onCancelled(FirebaseError firebaseError) {}
}
Note que em ambas implementações temos o método onCanceled() que é acionado quando há algum problema na leitura dos dados do Frebase por parte das classes listeners. O método onMoved() de CustomChildEventListener pode ser ignorado para o conteúdo desse post.
O parâmetro DataSnapshot en anbas as classes representa o último estado dos dados do Firebase, porém representa os dados de acordo com a granularidade dos listeners. O ValueEventListener recebe todos os dados abaixo do nodo "users" que está sendo utilizado, ou seja, se somente um dado do nodo "users" for atualizado todo o conteúdo de "users" será retornado no método onDataChange() pelo parâmetro DataSnapshot.
A granularidade de ChildEventLsistener é menor, no caso somente o nodo atualizado terá os dados retornados e não todo o conteúdo de "users".
Agora partimos para o primeiro teste utilizando somente a classe que implementa o listener ValueEventListener. O primeiro passo é atualizar o método onCreate() da MainActivity além de adicionar uma variável de instância nomeada customValueEventListener:
...
private Firebase firebase;
private CustomValueEventListener customValueEventListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
firebase = LibraryClass.getFirebase().child("users");
customValueEventListener = new CustomValueEventListener();
firebase.addValueEventListener( customValueEventListener );
}
...
O método addValueListener() adiciona a referência ao listener Value, porém também temos a opção de utilizar o método addListenerForSingleValueEvent() que ao invés de persistir com o listener enquanto ele não é removido do Firebase ou a APP é encerrada, esse método muda o comportamento de um ValueEventListener. Ele ativará o listener somente uma vez e logo depois o removerá do firebase.
Para podermos testar a utilização do listener ValueEventListener ainda falta a implementação do onDestroy() da MainActivity. Nesse método vamos remover o listener do firebase antes de destruir a Activity. Isso é também útil para que seja reduzida as chances de um OutOfMemoryException devido o aumento de memory leak na APP. Segue código:
...
@Override
protected void onDestroy() {
super.onDestroy();
firebase.removeEventListener( customValueEventListener );
}
...
Vou evitar a fadiga e não postarei a imagem da APP depois desse teste, pois o resultado já foi comentado anteriormente. Logo de inicio todos os dados do nodo "users" são apresentados e logo de pois, testando a atualização de somente um nodo abaixo de "users", todos os dados são noavemente retornados. Todos esse print é apresentado nos logs do AndroidStudio por meio da chamada a Log.i() em nosso código.
Para o teste com a classe que implementa o listener ChildEventListener apenas devemos alterar o nome Value por Child nas entidades adicionadas para o teste com a classe que implementa ValueEventListener, como abaixo:
...
private CustomChildEventListener customChildEventListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
firebase = LibraryClass.getFirebase().child("users");
customChildEventListener = new CustomChildEventListener();
firebase.addChildEventListener( customChildEventListener );
}
...
E no onDestroy():
...
@Override
protected void onDestroy() {
super.onDestroy();
firebase.removeEventListener( customChildEventListener );
}
...
A vantagem dessa implementação com ChildEventListener é que somente o nodo alterado é retornado em algum dos métodos listeners. Mesmo assim ela ainda não nos atende no objetivo de preencher nosso RecyclerView com o menor esforço possível, logo vamos para a utilização das classes de Firebase UI.
Nosso primeiro passo é a criação de um novo package chamado adapter. Esse package ficará no mesmo level que o package listener, no mesmo level das Activities.
Nosso segundo passo é a definição de uma classe que herda de RecyclerView.ViewHolder. Em nosso projeto é a classe UserViewHolder que ficará dentro de nosso novo package, adapter:
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.TextView;
public class UserViewHolder extends RecyclerView.ViewHolder {
public TextView text1;
public TextView text2;
public UserViewHolder(View itemView) {
super(itemView);
text1 = (TextView) itemView.findViewById(android.R.id.text1);
text2 = (TextView) itemView.findViewById(android.R.id.text2);
}
}
Os TextViews acima são referentes ao layout nativo android.R.layout.two_line_list_item que será informado na instanciação do adapter de nosso RecyclerView, lá na MainActivity.
O próximo passo é a criação de uma classe personalizada para ser o adapter, essa classe herdará de FirebaseRecyclerAdapter e também estará no package adapter:
import com.firebase.client.Query;
import com.firebase.ui.FirebaseRecyclerAdapter;
import br.com.thiengo.thiengocalopsitafbexample.domain.User;
public class UserRecyclerAdapter extends FirebaseRecyclerAdapter<User, UserViewHolder> {
public UserRecyclerAdapter(
Class<User> modelClass,
int modelLayout,
Class<UserViewHolder> viewHolderClass,
Query ref ){
super( modelClass, modelLayout, viewHolderClass, ref );
}
@Override
protected void populateViewHolder(
UserViewHolder userViewHolder,
User user, int i) {
userViewHolder.text1.setText( user.getName() );
userViewHolder.text2.setText( user.getEmail() );
}
}
Note que nessa precisamos definir o construtor, pois é nele que será passado o modelo de classe para deserialização dos dados do Firebase para um objeto POJO Java. O id do layout que será utilizado. O modelo de classe que implementa o ViewHolder e uma referência a Firebase (Query herda de Firebase). Esses parâmetros vão permitir que o trabalho pesado de apresentação e atualização do RecyclerView seja feito pelo Firebase UI.
O método populateViewHolder() apenas mapeia os dados nas views de UserViewHolder.
Nosso próximo passo é configurar a MainActivity para vincular esse novo adapter ao RecyclerView. No método init() e no método onDestroy() agora temos:
...
private void init(){
RecyclerView rvUsers = (RecyclerView) findViewById(R.id.rv_users);
rvUsers.setHasFixedSize( true );
rvUsers.setLayoutManager( new LinearLayoutManager(this));
adapter = new UserRecyclerAdapter(
User.class,
android.R.layout.two_line_list_item,
UserViewHolder.class,
firebase );
}
@Override
protected void onDestroy() {
super.onDestroy();
adapter.cleanup();
}
...
O método cleanup() é responsável por remover o listener de mudança de dados no Firebase, listener que já é adicionado pelo Firebase UI. Depois do teste temos:
Note que para atualização de dados não precisamos do apoio de scripts de push message, o Firebase server trabalha como parte ativa da comunicação entre server e APP além de disparar a atualização da base no formato Broadcast.
O projeto completo pode ser encontrado no GitHub: https://github.com/viniciusthiengo/nosso-chate
O projeto parte dois também é apresentado por completo no vídeo abaixo:
Abaixo a lista de posts / vídeos que podem lhe ajudar a compreender melhor o conteúdo desse post dois além de links úteis para acompanhar o conteúdo sobre Firebase:
RecyclerView, Material Design Android - Parte 2
PlayList sobre Firebase no canal Google Developers
Página da doc Firebase sobre Retriving Data, fonte do conteúdo de eventos de leitura desse post
Vlw
Comentários Facebook