Atualização de Dados, Firebase Android - Parte 3

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes! Você receberá um email de confirmação. Somente depois de confirma-lo é que eu poderei lhe enviar os conteúdos semanais exclusivos. Os artigos em PDF são entregues somente para os inscritos na lista.

Email inválido.
Blog /Android /Atualização de Dados, Firebase Android - Parte 3

Atualização de Dados, Firebase Android - Parte 3

Vinícius Thiengo
(7500) (7)
Go-ahead
"O método consciente de tentativa e erro é mais bem-sucedido que o planejamento de um gênio isolado."
Peter Skillman
Prototipagem Android
Capa do curso Prototipagem Profissional de Aplicativos
TítuloAndroid: Prototipagem Profissional de Aplicativos
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
Acessar Curso
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Lendo
TítuloTest-Driven Development: Teste e Design no Mundo Real
CategoriaEngenharia de Software
Autor(es)Mauricio Aniche
EditoraCasa do Código
Edição1
Ano2012
Páginas194
Conteúdo Exclusivo
Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba gratuitamente conteúdos Android sem precedentes!
Email inválido

Opa, blz?

Neste post damos continuidade a série de posts sobre o Firebase no Android, mais especificamente na construção de nossa APP de chat.

Vamos atualizar os dados do usuário conectado. Até o momento temos somente o dado de "name" que poderá ser atualizado, mas a maneira que vamos abordar é válida também para dados futuros como: idade, imagem (path dela), sobrenome, ...

Vamos estar utilizando também o método de refatoração de código Mover Acumulação Para Parâmetro Coletor e o padrão de implementação Cláusula de Guarda. Ambos ainda não comentados no Blog, mas bem simples de entender. O método de refatoração proposto será comentado na série Refatoração de Código.

Antes de prosseguir, abaixo há os posts anteriores sobre o Firebase no Android:

Eventos de Leitura e Firebase UI Android - Parte 2

Persistência Com Firebase Android - Parte 1

Note que no primeiro vídeo nós criamos um método para salvar o token atual de login do usuário conectado no SHaredPreferences, o problema nisso é que com esse token não conseguimos acessar os dados específicos do usuário conectado. Porém, salvamos, ainda no script de cadastro do primeiro post, o identificador único do usuário como sendo o nodo de acesso aos dados únicos dele na base Firebase. Dessa forma para que possamos obter esses dados únicos do usuário assim que ele se conecta a APP é obtendo também esse id de uma perssitência local. Vamos colocar essa feature utilizando o SharedPreferences como fizemos com o token.

O primeiro passo é atualizar nossa classe de domínio do problema, User, para também ter métodos de set e get id do SharedPreferences, segue trecho atualizado:

public class User {
public static String TOKEN = "br.com.thiengo.thiengocalopsitafbexample.domain.User.TOKEN";
public static String ID = "br.com.thiengo.thiengocalopsitafbexample.domain.User.ID";

...

public void saveIdSP(Context context, String token ){
LibraryClass.saveSP( context, ID, token );
}

public void retrieveIdSP(Context context ){
this.id = LibraryClass.getSP( context, ID );
}

...
}

 

Note que a partir desse post utilizaremos "retrieve" como sendo o prefixo de métodos que carregam os dados em um ou mais atributos da classe ao invés de retorná-los, que é o que é feito com os métodos de prefixo "get". Fique entendido também que nossos métodos com sufixo "SP" indicam acesso de dados no SharedPreferences.

Nosso próximo passo é atualizar nossa activity LoginActivity para logo no login, via token ou não, salvar também o id único do user utilizando nosso novo método da classe User, segue trechos do código que devem ser atualizados:

public class LoginActivity extends CommonActivity {
...

private void verifyUserLogged(){
if( firebase.getAuth() != null ){
callMainActivity();
}
else{
initUser();

if( !user.getTokenSP(this).isEmpty() ){
firebase.authWithPassword(
"password",
user.getTokenSP(this),
new Firebase.AuthResultHandler() {
@Override
public void onAuthenticated(AuthData authData) {
user.saveTokenSP( LoginActivity.this, authData.getToken() );
user.saveIdSP( LoginActivity.this, authData.getUid() );
callMainActivity();
}

@Override
public void onAuthenticationError(FirebaseError firebaseError) {}
}
);
}
}
}

private void verifyLogin(){
firebase.authWithPassword(
user.getEmail(),
user.getPassword(),
new Firebase.AuthResultHandler() {
@Override
public void onAuthenticated(AuthData authData) {
user.saveTokenSP( LoginActivity.this, authData.getToken() );
user.saveIdSP( LoginActivity.this, authData.getUid() );
closeProgressBar();
callMainActivity();
}

@Override
public void onAuthenticationError(FirebaseError firebaseError) {
showSnackbar( firebaseError.getMessage() );
closeProgressBar();
}
}
);
}
}

 

Dessa forma já temos o id para buscar os dados. Porém ainda não temos um método para buscar os dados, nem mesmo uma entidade que necessite desses dados. Logo vamos pra nossa activity UpdateActivity, que tem o seguinte código:

public class UpdateActivity extends AppCompatActivity {

private Toolbar toolbar;
private User user;
private AutoCompleteTextView name;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_update);

toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}

@Override
protected void onResume() {
super.onResume();
init();
}

private void init(){
toolbar.setTitle( getResources().getString(R.string.atualizar) );
name = (AutoCompleteTextView) findViewById(R.id.name);
user = new User();
}

public void update( View view ){
user.setName( name.getText().toString() );
}
}

 

Segue código XML de activity_update.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
android:fitsSystemWindows="true"
tools:context="br.com.thiengo.thiengocalopsitafbexample.UpdateActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_update" />

</android.support.design.widget.CoordinatorLayout>

 

E então o código de content_update.xml que é referenciado dentro de activity_update.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="br.com.thiengo.thiengocalopsitafbexample.UpdateActivity"
tools:showIn="@layout/activity_update">

<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<AutoCompleteTextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_name"
android:inputType="textPersonName"
android:maxLines="1"
android:singleLine="true" />

</android.support.design.widget.TextInputLayout>

<Button
android:id="@+id/bt_logout"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
android:onClick="update"
android:gravity="center"
android:text="@string/atualizar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>

 

A principio nada que já não tenha sido comentado nos vídeos e posts do Blog. Rodando a APP temos: 

Note que quando acessamos a activity UpdateActivity não é carregado o dado atual de nome no campo name, para que isso seja possível temos de utilizar nosso id de usuário e solicitar ao Firebase que nos entregue os dados necessários. Para isso vamos criar uma método nomeado contextDataDB() que vai ser responsável por receber um parâmetro do tipo Context para que possamos acessar o id no SharedPreferences e logo depois aplicaremos o cast nesse parâmetro para acessar uma implementação da Interface ValueEventListener que vai nos permitir acessar os dados assim que o Firebase os retornar da base remota (ou local em caso de cache). Acessar os dados depois que o Firebase os retornar? Sim, essa chamada é assíncrona. Segue código do método contextDataDB() na classe User:

public class User {
...

public void contextDataDB( Context context ){
retrieveIdSP( context );
Firebase firebase = LibraryClass.getFirebase().child("users").child( getId() );

firebase.addListenerForSingleValueEvent( (ValueEventListener) context );
}
}

 

Nosso próximo passo é implementar a Interface ValueEventListener em nossa UpdateActivity que é a entidade cliente desse acesso há dados. Note a utilização do método addListenerForSingleValueEvent() que como comentado no post dois dessa série, ele é disparado apenas uma vez, lembrando que sempre que algum dos métodos de listeners são utilizados eles sempre são disparados uma primeira logo depois de serem configurados no Firebase. Sendo assim esse método é idela para nosso script. Segue atualização da activity UpdateActivity:

public class UpdateActivity extends AppCompatActivity implements ValueEventListener {

...

private void init(){
toolbar.setTitle( getResources().getString(R.string.atualizar) );
name = (AutoCompleteTextView) findViewById(R.id.name);
user = new User();
user.contextDataDB( this );
}

...

@Override
public void onDataChange(DataSnapshot dataSnapshot) {
User u = dataSnapshot.getValue( User.class );
name.setText( u.getName() );
}

@Override
public void onCancelled(FirebaseError firebaseError) {}
}

 

Com a implementação da Interface temos de implementar dois métodos, onDataChange() e onCancelled(), porém esse último, a principio, não é útil a nós, logo deixe o corpo dele vazio. O outro, onDataChange(), é onde preencheremos nosso AutoCompleteTextView, name, logo no acesso a UpdateActivity. Segue imagem do conteúdo de nome pré-carregado:

Feito isso nosso próximo passo é permitir que o nome do usuário seja alterado, logo vamos desenvolver um novo método, dessa vez responsável pela atualização dos dados. Antes de prosseguir temos de definir algumas regras de negócio dessa feature de atualização.

Primeiro: esse método deve retornar um dado de sucesso ou falha para nossa UpdateActivity. Segundo: é possível implementar uma outra Interface para fazer com que nossa UpdateActivity seja cliente desse método update também, essa implementação de Interface é que permitirá nossa activity cliente ouvir o sucesso ou falha, porém queremos que o método update também consiga realizar sua tarefa sem um listener.

Definidas algumas regras de negócio o que devemos fazer primeiro é permitir que nossa UpdatedActivity seja uma entidade cliente do método updateDB() que criaremos em User. Para isso devemos implementar em UpdateActivity a Interface CompletionListener. Segue código:

public class UpdateActivity extends AppCompatActivity implements ValueEventListener, Firebase.CompletionListener {

...

@Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {

if( firebaseError != null ){
Toast.makeText( this, "Falhou: "+firebaseError.getMessage(), Toast.LENGTH_LONG ).show();
}
else{
Toast.makeText( this, "Atualização realizada com sucesso.", Toast.LENGTH_SHORT ).show();
}
}
}

 

Existe somente um método obrigatório nessa Interface, onComplete(). Com isso vamos poder informar ao user se os dados foram ou não atualizados. Como no condicional acima, caso firebaseError seja diferente de null, temos uma falha, caso contrário tudo foi atualizado sem problemas.

Nosso próximo passo é criar o método updateDB() em User, segue código:

public class User {

...

private void setNameInMap( Map<String, Object> map ) {
if( getName() != null ){
map.put( "name", getName() );
}
}

...

private void setEmailInMap( Map<String, Object> map ) {
if( getEmail() != null ){
map.put( "email", getEmail() );
}
}

...

public void updateDB( Firebase.CompletionListener... completionListener ){

Firebase firebase = LibraryClass.getFirebase().child("users").child( getId() );

Map<String, Object> map = new HashMap<>();
setNameInMap(map);
setEmailInMap(map);

if( map.isEmpty() ){
return;
}


if( completionListener != null && completionListener[0] != null ){
firebase.updateChildren(map, completionListener[0]);
}
else{
firebase.updateChildren(map);
}
}
...
}

 

Note que temos trechos de código não previstos nos comentários anterior desse post, mas são simples de entender.

Primeiro é a notação utilizada na assinatura do método, Firebase.CompletionListener..., que nos indica que o método updateDB() pode ser utilizado com ou sem parâmetros do tipo informado. Essa é uma outra notação para utilização de array em Java, porém mais poderosa por permitir que o método também seja invocado da seguinte maneira: updateDB(). Sem parâmetros.

Os métodos setNameInMap() e setEmailInMap() são responsáveis por nos deixar implementar o método de refatoração Mover Acumulação para Parâmetro Coletor. Nosso parâmetro coletor no caso é a variável Map<String, Object> map que esta no método updateDB(). Caso name ou email tenham dados, em nossa lógica de negócio, isso indica que devemos sim atualizá-los junto ao Firebase, caso contrário eles não entram no parâmetro map.

Note que os nomes de keys Firebase, "name" e "email", da maneira como estão explicitados diretamente no script podemos ter sérios problemas de atualização no futuro se precisarmos dessas keys em outros trechos do código, esses valores soltos são conhecidos como "valores mágicos", ou seja, seria prudente termos uma constante para cada uma dessas keys dos métodos setNameInMap() e setEmailInMap(), porém como ainda nesse exemplo estamos utilizando cada key apenas uma vez e em seus respectivos métodos vamos manter dessa forma.

Note que o condicional que verifica se map é vazio, if( map.isEmpty() ), é na verdade a implementação de um padrão de implementação chamado Cláusula de Guarda, que tem como objetivo evitar que tenhamos condicionais aninhados em nosso código, consequetemente previnindo mais bugs e mantendo um código mais legível.

Agora atualizamos o método update de UpdateActivity:

public class UpdateActivity extends AppCompatActivity implements ValueEventListener, Firebase.CompletionListener {

...

public void update( View view ){
user.retrieveIdSP( this );
user.setName( name.getText().toString() );
user.updateDB( this );
}

...
}

 

Depois dessas atualizações podemos sem problemas seguir com a atualização do nome do usuário como na imagem abaixo:

Note que ainda podemos colocar uma nova variável de instância na classe User, seria ela Firebase firebase, mas por hora vamos seguir dessa forma, caso seja necessária ainda mais utilização de firebase em User podemos prosseguir com essa atualização.

Note que o código completo pode ser encontrado no GitHub do projeto: https://github.com/viniciusthiengo/nosso-chate

No vídeo abaixo temos a implementação desse post do zero até o final, de forma detalhada.

Seguem links para melhor entendimento do post:

Firebase doc, Saving Data

SharedPreferences

Se quiser já ir adiantando o conteúdo do próximo post dessa série, segue link referência:

Firebse doc, Email & Password Authentication

Abraço

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes!
Email inválido

Relacionado

Edicão de Imagem no Android Com Adobe Creative SDKEdicão de Imagem no Android Com Adobe Creative SDKAndroid
Utilizando BottomSheet Material Design no AndroidUtilizando BottomSheet Material Design no AndroidAndroid
Refatoração de Código: Substituir Lógica Condicional Por StrategyRefatoração de Código: Substituir Lógica Condicional Por StrategyAndroid
Refatoração Para PadrõesRefatoração Para PadrõesLivros

Compartilhar

Comentários Facebook

Comentários Blog (7)

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...
Rhenan Cocev (2) (0)
21/03/2018
Boa tarde Thiengo, tudo bem? Eu estou desenvolvendo um app e estou implementando login e senha do firebase, e vi que no authentication tem alguns métodos, e eu estou tentando implementar o Verificação de endereço de email, quando eu preencho  o formulario e cadastro ele envia um email para o endereço que eu coloquei com um link para verificação.... Porém está meio sem função isso no meu app, pois se eu nao verificar clicando no link ele faz o login da mesma forma... tem uma maneira de fazer o usuario validar o email e caso ele não valide ele não consegue entrar com o usuario e senha?
a mesma coisa na hora de salvar os dados, tem como só concluir o cadastro se ele validar o email? já tentei de tudo estou pesquisando a um tempo já e cheguei até seu canal que me ensinou muita coisa... Mas estou empacado nisso
Responder
Vinícius Thiengo (1) (0)
25/03/2018
Rhenan, tudo bem aqui.

Hoje quando trabalho com sistema de login ou eu utilizo o Account Kit API ou eu crio todo o algoritmo do zero, apesar de também recomendar o sistema de autenticação do Firebase.

Mas fiquei na dúvida quanto ao que você informou: o email de confirmação é um algoritmo a parte que você mesmo desenvolveu ou é do próprio sistema de autenticação do Firebase?

Pois caso seja do Firebase seria esperado isso: que o usuário somente conseguisse acessar a área de usuários autenticados depois de confirmar o email dele.

Com o Account Kit esse fluxo de login e confirmação é trivial e ele já gerencia tudo, confirmação por email ou por SMS. Caso queira ao menos conhecer está API, veja o conteúdo do link a seguir: https://www.thiengo.com.br/como-implementar-o-account-kit-login-em-seu-aplicativo-android

De qualquer forma, o que pode fazer é colocar em sua base de dados, mesmo que não seja o Firebase Database, uma flag que indique "autenticação realizada", ou seja, depois de o usuário ter realizado o login / cadastro ele ainda não está conectado (terá um boolean em uma tabela de login de usuário) enquanto não entrar em email e clicar em um link que contém um hash de confirmação de proprietário de conta.

Rhenan, note que esse tipo de algoritmo de login é comum quando não se tem também um formulário de cadastro, digo, o formulário de cadastro pode até existir, mas seria algo exposto ao usuário somente depois do login (o real cadastro) ter sido realizado. Exatamente o que faz o fluxo de login com o Account Kit API.

Com o algoritmo de flag que indiquei anteriormente, você teria que no código Android verificar se a flag no banco de dados, referente ao usuário, estaria ou não com o valor "true", que indicaria "conectado", ou seja, mesmo o Firebase apontando que ele já esteja conectado você teria um outro algoritmo para assegurar isso.

Abraço.
Responder
Jasian Cardoso (2) (0)
06/12/2016
Boa tarde thiengo, como faço para ter uma aplicação offiline mais que no primeiro acesso faça download dos dados do firebase para um banco local sqlite?
Responder
Vinícius Thiengo (1) (0)
06/12/2016
Jasian, tudo bem?

Ainda não trabalhei com dados offline que venham do Firebase, mas com o SQLite de suporte para dados locais, sim.

Nesse caso o que faço é carregar os dados de meu backend Web (PHP + MySQL) e então, assim que os dados chegam na APP, abro uma Thread de background e nessa Thread o script salva os dados no SQLite.

As tabelas do SQlite de meus projetos têm exatamente as mesmas estruturas das tabelas do banco de dados remoto, isso, pois assim que o script tenta acessar os dados online e não consegue, a exata mesma requisição é enviada local para o SQLite e a resposta é exatamente no mesmo formato quando acontece com a conexão com o backend remoto.

Na APP do Blog faço isso, para conexão remota utilizo o Volley, mas nem vou disponibilizar o link dele aqui, pois recomendo que utilize o Retrofit.

Mesmo assim, a solução acima, digo, utilizando o Retrofit ou o Volley, não são úteis a ti, pois você já está com o Firebase, então somente a parte de salvar os dados no SQLite em uma Thread de background é que acho que lhe ajudaria.

Porém o Firebase tem um recurso para trabalho com ele offline, veja no link a seguir:

https://firebase.google.com/docs/database/android/offline-capabilities?hl=pt-br

Tente o recurso do link acima, pois assim você continuara trabalhando com uma base NoSQL, não precisará de unir uma relacional com uma não relacional.

Abraço.
Responder
15/04/2016
Olá Thiengo! Esta serie sobre o Firebase esta muito boa e realmente veio em boa hora para todos nós Dev. Android.

Abraços, força e continue com este excelente trabalho!
Responder
Johnathan (4) (0)
14/04/2016
Olá Thiengo, todas as series são formidáveis, acho que você já deve estar sabendo que o Parse vai aposentar e eles colocaram todo o projeto no Github, teria como você fazer um post sobre esse assunto ou vídeo futuro, acredito que vai ajudar muito pessoas que usavam o Parse
Responder
Vinícius Thiengo (3) (0)
15/04/2016
Fala Johnathan, blz?
Estou ciente dessa aposentadoria. Vou ver essa do código, estou imaginando o pouco de codificação que deve ter (sei!). Mt obg pela dica. Abraço
Responder