Atualização de Dados, Firebase Android - Parte 3
(7588) (7)
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?
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:
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
Comentários Facebook