Múltiplos Links de Autenticação e Correção de Código, Firebase Android - Parte 10

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog. Você receberá um email de confirmação. Somente depois de confirma-lo é que poderei lhe enviar os conteúdos exclusivos.

Email inválido.
Blog /Android /Múltiplos Links de Autenticação e Correção de Código, Firebase Android - Parte 10

Múltiplos Links de Autenticação e Correção de Código, Firebase Android - Parte 10

Vinícius Thiengo
(1933) (2)
Go-ahead
"Esse tem sido um dos meus mantras: foco e simplicidade. Simples pode ser mais difícil do que o complexo. Você tem que trabalhar duro para conseguir o seu pensamento limpo para torná-lo simples. Mas vale a pena no final, porque uma vez que você chegue lá, você pode mover montanhas."
Steve Jobs
Kotlin Android
Capa do livro Desenvolvedor Kotlin Android - Bibliotecas para o dia a dia
TítuloDesenvolvedor Kotlin Android - Bibliotecas para o dia a dia
CategoriasAndroid, Kotlin
AutorVinícius Thiengo
Edição
Capítulos19
Páginas1035
Acessar Livro
Treinamento Oficial
Android: Prototipagem Profissional de Aplicativos
CursoAndroid: Prototipagem Profissional de Aplicativos
CategoriaAndroid
InstrutorVinícius Thiengo
NívelTodos os níveis
Vídeo aulas186
PlataformaUdemy
Acessar Curso
Receitas Android
Capa do livro Receitas Para Desenvolvedores Android
TítuloReceitas Para Desenvolvedores Android
CategoriaDesenvolvimento Android
AutorVinícius Thiengo
Edição
Ano2017
Capítulos20
Páginas936
Acessar Livro
Código Limpo
Capa do livro Refatorando Para Programas Limpos
TítuloRefatorando Para Programas Limpos
CategoriaEngenharia de Software
AutorVinícius Thiengo
Edição
Capítulos46
Páginas599
Acessar Livro
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Conteúdo Exclusivo
Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Opa, blz?

Nesse post continuamos com a série Firebase Android, dessa vez abordando a funcionalidade que nos permite linkar várias contas de login em apenas uma conta Firebase, isso para que o usuário possa utilizar a conta que preferir, porém sempre acessando os mesmos dados no Firebase (em nossa APP de chat: profile, mensagens e grupos).

Nesse post também vamos abordar algumas alterações necessárias para corrigir bugs deixados desde o último post.

O código completo do projeto está disponível no seguinte repo GitHub: https://github.com/viniciusthiengo/nosso-chate

Os links dos posts já liberados dessa série estão no final do post.

Vamos começar com as alterações na classe User. Nós não precisamos mais dos métodos retrieveSP() e saveSP(). Por que não? Porque vamos utilizar o FirebaseUser.getUid(), esse remove a necessidade de um SharedPreferences. Não esqueça de também remover a constante ID. Abaixo as atualizações da classe User:

public class User {
public static String PROVIDER = "br.com.thiengo.thiengocalopsitafbexample.domain.User.PROVIDER";
/* CONSTANTE ID REMOVIDA */

/* MÉTODOS retieceIdSP() E saveIdSP() REMOVIDOS */

/* MÉTODOS generateCryptPassword() E generateCryptNewPassword() REMOVIDOS */

/* MÉTODO ABAIXO ATUALIZADO */
public void removeDB( DatabaseReference.CompletionListener completionListener ){
DatabaseReference firebase = LibraryClass.getFirebase().child("users").child( getId() );
firebase.setValue(null, completionListener);
}

/* MÉTODO ABAIXO ATUALIZADO */
public void contextDataDB( Context context ){
DatabaseReference firebase = LibraryClass.getFirebase().child("users").child( getId() );
firebase.addListenerForSingleValueEvent( (ValueEventListener) context );
}
}

 

Veja que também removemos os métodos referentes a criptografia das variáveis de instância password e newPassword, respectivamente generateCryptPassword() e generateCryptNewPassword(). isso pois não precisamos mais dessa caracteristica de segurança, digo, não a criptograifa criada por nós. Vamos deixar o próprio Firebase lhe dar com isso. Note que também é necessária a remoção da classe CryptWithMD5 do pacote util.

Outro ponto de alteração foi o método removeDB(). Por que? Esse mais precisamente por causa do algoritmo de remoção de conta de usuário em RemoveUserActivity.

Nessa activity, logo depois de deletarmos o usuário da base de autenticação do Firebase, precisaríamos de deletar os dados dele na base de dados NoSQL também, porém foi percebido que com o script atual de finalização de activity (finish()) logo depois de deletar o usuário, com esse script, os dados do usuário deletado não eram removidos da base NoSQL, somente da base de autenticação. Então dados não mais úteis ficavam na base Firebase.

A solução foi: somente sair de RemoveUserActivity, digo, utilizar o finish(), depois que os dados do usuário no NoSQL Firebase tivessem sido removidos. Para que esse novo script seja possível temos de realizar uma implementação em removeDB() semelhante a implementação em updateDB(). Segue:

...
public void removeDB( DatabaseReference.CompletionListener completionListener ){
DatabaseReference firebase = LibraryClass.getFirebase().child("users").child( getId() );
firebase.setValue(null, completionListener);
}
...

 

Apenas acrescentamos um listener de tarefa finalizada, CompletionListener, que será utilizado como segundo argumento em uma sobrecarga de setValue(). As Activitties responsáveis por utilizar esse método da classe User deverão implementar essa Interface, mesmo que em forma de composição (atributos implementando ela).

Podemos ver como exemplo parte do código atualizado de RemoveUserActivity:

public class RemoveUserActivity extends AppCompatActivity
implements ValueEventListener, DatabaseReference.CompletionListener {

...

private void deleteUser(){
FirebaseUser firebaseUser = mAuth.getCurrentUser();

if( firebaseUser == null ){
return;
}

firebaseUser.delete().addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {

if( !task.isSuccessful() ){
return;
}

user.removeDB( RemoveUserActivity.this ); /* TRECHO ATUALIZADO */
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Toast.makeText(
RemoveUserActivity.this,
e.getMessage(),
Toast.LENGTH_SHORT
).show();
}
});
}

...

/* MÉTODO ADICIONADO */
@Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
Toast.makeText(
RemoveUserActivity.this,
"Conta removida com sucesso",
Toast.LENGTH_SHORT
).show();
finish();
}
}

 

Note que depois das remoções praticadas em User, muito do código do projeto vai "quebrar". Boa parte será resolvida somente com a remoção de chamadas de métodos que não mais existem nessa classe. Porém tem uma alteração crítica (pois temos de fazer uma por uma na mão) ainda a fazer.

Nas activities: RemoveUserActivity, UpdateActivity, UpdateLoginActivity e UpdatedPasswordActivity. Nessas classes têm uma chamada ao método contextDataDB() de User. Esse método requer o ID do usuário no Firebase para poder prosseguir o processamento, porém a atribuição que tinha dentro dele para a variável de instância id foi removida junto ao método retrieveIdSP().

Então o que devemos fazer? Colocar essa atribuição sempre antes da chamada ao método contextDataDB(). Primeiro na activity RemoveUserActivity:

...
private void init(){
toolbar.setTitle( getResources().getString(R.string.remove_user) );
password = (EditText) findViewById(R.id.password);

user = new User();
user.setId( mAuth.getCurrentUser().getUid() ); /* LINHA ADICIONADA */
user.contextDataDB( this );
}
...

 

Agora em UpdateActivity:

...
private void init(){
toolbar.setTitle( getResources().getString(R.string.update_profile) );
name = (AutoCompleteTextView) findViewById(R.id.name);

user = new User();
user.setId( FirebaseAuth.getInstance().getCurrentUser().getUid() ); /* LINHA ADICIONADA */
user.contextDataDB( this );
}
...

 

Também em UpdateLoginActivity:

...
private void init(){
toolbar.setTitle( getResources().getString(R.string.update_login) );
newEmail = (AutoCompleteTextView) findViewById(R.id.email);
password = (EditText) findViewById(R.id.password);

user = new User();
user.setId( mAuth.getCurrentUser().getUid() ); /* LINHA ADICIONADA */
user.contextDataDB( this );
}
...

 

E então em UpdatePasswordActivity:

...
private void init(){
toolbar.setTitle( getResources().getString(R.string.update_password) );
newPassword = (EditText) findViewById(R.id.new_password);
password = (EditText) findViewById(R.id.password);

user = new User();
user.setId( mAuth.getCurrentUser().getUid() );
user.contextDataDB( this );
}
...

 

Com isso somente falta falar sobre uma outra alteração importante. Em LoginActivity, mais precisamente no método getFirebaseAuthResultHandler() temos agora o seguinte novo código dentro de onAuthStateChanged():

...
FirebaseUser userFirebase = firebaseAuth.getCurrentUser();

if( userFirebase == null ){
return;
}

if( user.getId() == null
&& isNameOk( user, userFirebase ) ){

user.setId( userFirebase.getUid() );
user.setNameIfNull( userFirebase.getDisplayName() );
user.setEmailIfNull( userFirebase.getEmail() );
user.saveDB();
}

callMainActivity();
...

 

O if( userFirebase == null ) é na verdade a aplicação do padrão Claúsula de Guarda para que caso o usuário não esteja ainda conectado o código seguinte a esse condicional nem mesmo seja alcançado.

Caso o usuário esteja conectado, apenas verificamos se há ou não a necessidade de atualização de dados, isso quando há um ID e um nome válido. Porém desde o post anterior, GitHub Login, Firebase Android - Parte 9, sabemos que o ID sempre vem quando o usuário estiver conectado, o name preenchido já não é garantido e mesmo nesse caso, com o usuário conectado, devemos chamar a MainActivity, por isso agora a chamada a callMainActivity() está fora do condicional de alteração de dados.

Com isso finalizamos os comentários sobre as principais atualizações no código do projeto Chat Firebase Android.

Colocando a funcionalidade de múltiplos logins em uma conta Firebase:

Agora podemos prosseguir com a verdadeira evolução da APP, vamos começar dando um Ctrl + C e Ctrl + V na activity LoginActivity. Coloque o nome dessa nova classe como seno LinkAccountsActivity. Segue:

public class LinkAccountsActivity extends CommonActivity
implements GoogleApiClient.OnConnectionFailedListener {

/* TODO O RESTANTE DO CÓDIGO TAMBÉM PRESENTE EM LoginActivity */
}

 

No AndroidManifest.xml também devemos adicionar essa activity:

...
<application xmlns:tools="http://schemas.android.com/tools"
android:name=".CustomApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">

...
<activity
android:name=".LinkAccountsActivity"
android:label="@string/title_activity_link_accounts" />
...
</application>
...

 

De onde vem title_activity_link_accounts? Essa é uma das várias novas referências a strings em /res/values/strings.xml:

<resources>
...
<string name="action_link">Link convencional login</string>
<string name="action_unlink">Unlink convencional login</string>
<string name="action_link_facebook">Link Facebook login</string>
<string name="action_unlink_facebook">Unlink Facebook login</string>
<string name="action_link_google">Link Google login</string>
<string name="action_unlink_google">Unlink Google login</string>
<string name="action_link_twitter">Link Twitter login</string>
<string name="action_unlink_twitter">Unlink Twitter login</string>
<string name="action_link_github">Link GitHub login</string>
<string name="action_unlink_github">Unlink GitHub login</string>
<string name="link_accounts">Link contas</string>
<string name="title_activity_link_accounts">Link contas</string>
</resources>

 

Também devemos dar um Ctrl + C e Ctrl + V nos layouts de LoginActivity, mais precisamente os XMLs: activity_login.xml e content_login.xml. Consequentemente teremos o activity_link_accounts.xml:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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"
tools:context=".LinkAccountsActivity"
android:fillViewport="true">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

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

</RelativeLayout>
</ScrollView>

 

E o content_link_accounts.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:gravity="center_horizontal"
android:layout_below="@+id/logo"
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=".LinkAccountsActivity"
tools:showIn="@layout/activity_link_accounts">

<ProgressBar
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:id="@+id/login_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="gone" />

<ScrollView
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:id="@+id/login_form"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:id="@+id/email_login_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<android.support.design.widget.TextInputLayout
android:id="@+id/til_email"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<AutoCompleteTextView
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:maxLines="1"
android:singleLine="true" />

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

<android.support.design.widget.TextInputLayout
android:id="@+id/til_password"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_password"
android:imeActionId="@+id/login"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true" />

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


<Button
android:onClick="sendLoginData"
android:id="@+id/email_sign_in_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_sign_in"
android:textStyle="bold" />

<Button
android:onClick="sendLoginFacebookData"
android:id="@+id/email_sign_in_facebook_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_sign_in_facebook"
android:textStyle="bold" />

<Button
android:onClick="sendLoginGoogleData"
android:id="@+id/email_sign_in_google_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_sign_in_google"
android:textStyle="bold" />

<Button
android:onClick="sendLoginTwitterData"
android:id="@+id/email_sign_in_twitter_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_sign_in_twitter"
android:textStyle="bold" />

<Button
android:onClick="sendLoginGithubData"
android:id="@+id/email_sign_in_github_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_sign_in_github"
android:textStyle="bold" />

</LinearLayout>
</ScrollView>
</RelativeLayout>

Esse último somente removemos os TextViews referentes a recuperação de acesso e ao cadastro. Também colocamos ids nos TextInputLayout de email e password.

Agora voltamos as atualizações em nossa nova activity, LinkAccountsActivity. Começando com a remoção de algumas entidades.

Remova os seguintes métodos (e suas respectivas referências): callMainActivity(), verifyLogin(), verifyLogged(), callSignUp(), callReset(), onStop(), onStart()getFirebaseAuthResultHandler()isNameOk().

Remova os seguintes atributos (e suas respectivas referências): mAuthListener.

O AuthListener também? Sério? Sim, nessa activity não mais precisaremos dele.

Note que em content_link_accounts.xml temos ainda todos os buttons de login e seus respectivos labels de login. Esses labels não fazem sentido aqui nessa acvitiy.

Ok, então por que você não os alterou direto no XML? Porque cada button poderá variar com dois labels, essa variação será realizada no Java API, mais precisamente em nosso novo método initButtons():

...
private void initButtons(){

setButtonLabel(
R.id.email_sign_in_button,
EmailAuthProvider.PROVIDER_ID,
R.string.action_link,
R.string.action_unlink,
R.id.til_email,
R.id.til_password
);

setButtonLabel(
R.id.email_sign_in_facebook_button,
FacebookAuthProvider.PROVIDER_ID,
R.string.action_link_facebook,
R.string.action_unlink_facebook
);

setButtonLabel(
R.id.email_sign_in_google_button,
GoogleAuthProvider.PROVIDER_ID,
R.string.action_link_google,
R.string.action_unlink_google
);

setButtonLabel(
R.id.email_sign_in_twitter_button,
TwitterAuthProvider.PROVIDER_ID,
R.string.action_link_twitter,
R.string.action_unlink_twitter
);

setButtonLabel(
R.id.email_sign_in_github_button,
GithubAuthProvider.PROVIDER_ID,
R.string.action_link_github,
R.string.action_unlink_github
);
}
...

 

Note que temos uma chamada setButtonLabel() para cada button. O primeiro argumento de cada chamada é o id do button (um inteiro). O segundo argumento é o id do provider (uma String), os terceiro e quarto argumentos são os ids das strings que estão presentes em /res/values/strings.xml, strings referentes aos labels de quando a conta referente ao provider está linked ou unliked. O último argumento é um varargs (já falamos desse cara em posts anteriores dessa série) para valores inteiros. Em nosso caso serão os ids de views TextInputView.

Por que esses ids como valores de nosso varargs? Porque além de mudar o label do button, vamos também esconder ou apresentar os TextInputViews no layout, mais precisamente os referentes a login convencional, email e senha.

O método setButtonLabel() é o que encapsula o código de alteração de label:

...
private void setButtonLabel(
int buttonId,
String providerId,
int linkId,
int unlinkId,
int... fieldsIds ){

if( isALinkedProvider( providerId ) ){

((Button) findViewById( buttonId )).setText( getString( unlinkId ) );
showHideFields( false, fieldsIds );
}
else{
((Button) findViewById( buttonId )).setText( getString( linkId ) );
showHideFields( true, fieldsIds );
}
}
...

 

Nada de complexo nesse método, apenas temos de aplicar o cast ao resultado de findViewById() para que seja possível utilizar a chamada a setText(). Segue código do método isAlinkedProvider():

...
private boolean isALinkedProvider( String providerId ){

for(UserInfo userInfo : mAuth.getCurrentUser().getProviderData() ){

if( userInfo.getProviderId().equals( providerId ) ){
return true;
}
}
return false;
}
...

 

A chamada mAuth.getCurrentUser().getProviderData() vai nos permitir acessar os providers vinculados a conta do usuário atualmente conectado. Logo a estrutura de seleção no corpo do loop for() será responsável por verificar se o provider em teste está ou não vinculado ao usuário.

Agora o método showHideFields() que será responsável por apresentar ou esconder os views referenciados pelos ids em nosso varargs fieldsIds:

...
private void showHideFields( boolean status, int... ids ){
for( int id : ids ){
findViewById( id ).setVisibility( status ? View.VISIBLE : View.GONE );
}
}
...

 

Feito isso já podemos colocar a chamada a initButtons() no método initViews():

...
protected void initViews(){
email = (AutoCompleteTextView) findViewById(R.id.email);
password = (EditText) findViewById(R.id.password);
progressBar = (ProgressBar) findViewById(R.id.login_progress);

initButtons();
}
...

 

Com isso os buttons já serão apresentados com os labels corretos.

Nosso próximo passo é colocar ação no clique dos buttons. Ok, mas estamos utilizando os mesmos métodos de login da LoginActivity! Isso, esses métodos persistem sendo os mesmos. Na verdade vão sofrer uma pequena modificação, mas os algoritmos referentes a login serão os mesmo.

Antes de prosseguir com o método responsável pelo link de providers a conta do usuário atual, vamos criar um novo método, mais precisamente o método accessEmailLoginData(), pois com a retirada do método verifyLogin() não temos onde vincular o link de login convencional (email e password) ao usuário conectado. Segue código:

...
private void accessEmailLoginData( String email, String password ){
accessLoginData(
"email",
email,
password
);
}
...

 

Agora o método sendloginData() atualizado:

...
public void sendLoginData( View view ){
if( isALinkedProvider( EmailAuthProvider.PROVIDER_ID ) ){
unlinkProvider( EmailAuthProvider.PROVIDER_ID );
return;
}

openProgressBar();
initUser();
accessEmailLoginData( user.getEmail(), user.getPassword() ); /* LINHA ADICIONADA */
}
...

 

E esse condicional com um método unlinkProvider()? Será explicado logo depois do método de link de providers.

Note que nossa principal modificação ficará no método accessLoginData():

...
private void accessLoginData(final String provider, String... tokens ){
if( tokens != null
&& tokens.length > 0
&& tokens[0] != null ){

AuthCredential credential = FacebookAuthProvider.getCredential( tokens[0]);
credential = provider.equalsIgnoreCase("google") ? GoogleAuthProvider.getCredential( tokens[0], null) : credential;
credential = provider.equalsIgnoreCase("twitter") ? TwitterAuthProvider.getCredential( tokens[0], tokens[1] ) : credential;
credential = provider.equalsIgnoreCase("github") ? GithubAuthProvider.getCredential( tokens[0] ) : credential;
credential = provider.equalsIgnoreCase("email") ? EmailAuthProvider.getCredential( tokens[0], tokens[1] ) : credential;

mAuth
.getCurrentUser()
.linkWithCredential( credential )
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
closeProgressBar();
closeGitHubDialog();

if( !task.isSuccessful() ){
return;
}

initButtons();
showSnackbar("Conta provider "+provider+" vinculada com sucesso.");
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
showSnackbar("Error: "+e.getMessage());
}
});
}
else{
mAuth.signOut();
}
}
...

 

A estrutura do metodo acima é quase a mesma do método accessLoginData() de LoginActivity. Porém agora temos a verificação do EmailAuthProvider e o método principal é o linkWithCredential() que é responsável por vincular o provider a conta atual.

Caso a conta do provider já esteja vincualda ao outro usuário, uma mensagem de erro é disparada em onFailure(). Será essa apresentada falando dessa vinculação existente. Caso dê tudo certo, em onComplete() damos close em todo o necessário e então atualizamos os labels dos buttons via chamada a initButtons() e logo depois imprimimos uma mensagem de vinculção realizada com sucesso.

No onComplete() temos novamente a utilização do padrão Claúsula de Guarda, pois mesmo com falha o método é executado. Note que se não chamássemos o método initButtons() esses permaneceriam com os mesmos labels, mesmo tendo os estados dos providers alterados depois da vinculação.

Ok, e esse método closeGitHubDialog()? Esse método é responsável por encerrar o Dialog do GitHub Login caso essa rede seja utilizada para vinculação de conta. Pois como não temos uma chamada a outra activity como em LoginActivity, se esse algoritmo de fechamento de Dialog não for utilizado ele persistirá aberto na tela.

Nossa primeira tarefa com esse algoritmo do GitHub é criar uma variável de instância do tipo Dialog, logo depois pegar a referência do Dialog utilizado para apresentar a tela de login do GitHub e então no método closeGitHubDialog() verificar se é ou não necessário o fechamento do Dialog:

...
public class LinkAccountsActivity extends CommonActivity
implements GoogleApiClient.OnConnectionFailedListener {
...

private Dialog gitHubDialog;
...

private void closeGitHubDialog(){
if( gitHubDialog != null ){
gitHubDialog.dismiss();
}
}
...

public void sendLoginGithubData( View view ){

....

AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.setTitle("Login GitHub");
alert.setView(webView);
gitHubDialog = alert.show(); /* OBTENDO A REFERÊNCIA */
}
...
}
...

 

Ok, agora temos de implementar nosso método unlinkProvider(). Segue código:

...
private void unlinkProvider(final String providerId ){

mAuth
.getCurrentUser()
.unlink( providerId )
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {

if( !task.isSuccessful() ){
return;
}

initButtons();
showSnackbar("Conta provider "+providerId+" desvinculada com sucesso.");

if( isLastProvider( providerId ) ){
user.setId( mAuth.getCurrentUser().getUid() );
user.removeDB( LinkAccountsActivity.this );
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
showSnackbar("Error: "+e.getMessage());
}
});
}
...

 

Dessa vez chamando o método unlink() que somente precisa do id do provider como argumento para realizar a desvinculação. Note que no método onComplete() temos novamente a chamada a initButtons(), pois depois da desvinculação feita com sucesso algum outro button terá um novo label.

Você deve estar se perguntando: Por que precisamos da estrutura de seleção no final do método onComplete()? Essa estrutura de seleção é na verdade responsável por verificar se esse era o último provider vinculado ao usuário, pois caso sim, ele de forma implícita removeu a conta dele de nossa APP de Chat.

Como assim? Na autenticação Firebase, mais precisamente em uma conta Firebase em nosso chat, o usuário tem de ter ao menos um provider vinculado a ele, caso contrário nem mesmo realizar o login será possível. Se ele aplicar o unlink no único provider ainda vinculado a ele, estará na verdade removendo a conta dele do APP.

Vamos ao método isLastProvider():

private boolean isLastProvider( String providerId ){
int size = mAuth.getCurrentUser().getProviders().size();
return(
size == 0
|| (size == 1 && providerId.equals(EmailAuthProvider.PROVIDER_ID) )
);
}

 

A importância do String providerId é que temos um caso especial. Provavelmente um bug não permite que o provider de id "password" da entidade EmailAuthProvider seja removido de imediato. Ou seja, mesmo ele tendo sido desvinculado o getProviders() do FirebaseUser continua utilizando esse como provider válido ao usuário atual. Por isso um condicional especial somente para essa versão de provider.

Veja que no onComplete() estamos utilizando o método removeDB(), logo temos também de implementar a Interface CompletionListener:

public class LinkAccountsActivity extends CommonActivity
implements GoogleApiClient.OnConnectionFailedListener, DatabaseReference.CompletionListener {

...

@Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
mAuth.getCurrentUser().delete();
mAuth.signOut();
finish();
}
}

 

Esse método onComplete() do CompletionListener somente será chamado caso seja o último provider sendo desvinculado. Ele deleta o usuário da base de autenticação, dá o signout e logo depois finaliza a Activity. Isso é necessário, pois o usuário removeu de forma implícita a conta dele do APP.

Ok, a classe LinkAccountsActivity está completa. Mas como vamos acessá-la?

Pela MainActivity, mais precisamente pelos menus da MainActivity. Menus? Sim, em ambos os casos, conectado pelo login convencional ou pelo login social, temos menus XML distintos, mas mesmo assim devemos propiciar ao usuário a oportunidade de vincular mais contas a conta atual dele.

Segue atualização em ambos os menus XML. Començando pelo menu.xml:

<?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/update_profile"
app:showAsAction="never" />

<item
android:id="@+id/action_update_login"
android:orderInCategory="100"
android:title="@string/update_login"
app:showAsAction="never" />

<item
android:id="@+id/action_update_password"
android:orderInCategory="100"
android:title="@string/update_password"
app:showAsAction="never" />

<item
android:id="@+id/action_link_accounts"
android:orderInCategory="100"
android:title="@string/link_accounts"
app:showAsAction="never" />

<item
android:id="@+id/action_remove_user"
android:orderInCategory="100"
android:title="@string/remove_user"
app:showAsAction="never" />

<item
android:id="@+id/action_logout"
android:orderInCategory="100"
android:title="@string/logout"
app:showAsAction="never" />
</menu>

 

Logo depois o menu_social_network_logged.xml:

<?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_link_accounts"
android:orderInCategory="100"
android:title="@string/link_accounts"
app:showAsAction="never" />

<item
android:id="@+id/action_logout"
android:orderInCategory="100"
android:title="@string/logout"
app:showAsAction="never" />
</menu>

 

Então devemos atualizar o método onOptionsItemSelected() da MainActivity para refletir nossa alteração nos arquivos de menu:

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_update_login){
startActivity(new Intent(this, UpdateLoginActivity.class));
}
else if(id == R.id.action_update_password){
startActivity(new Intent(this, UpdatePasswordActivity.class));
}
else if(id == R.id.action_link_accounts){ /* ESSE É O CONDICIONAL ADICIONADO */
startActivity(new Intent(this, LinkAccountsActivity.class));
}
else if(id == R.id.action_remove_user){
startActivity(new Intent(this, RemoveUserActivity.class));
}
else if(id == R.id.action_logout){
FirebaseAuth.getInstance().signOut();
finish();
}

return super.onOptionsItemSelected(item);
}

 

Com isso podemos rodar nosso APP e acessar nossa LinkAccountsActivity:

E depois de algumas vinculações temos a seguinte configuração em nosso dashboard Firebase:

Realize vários testes como também feito no vídeo dessa implementação. Aqui finalizamos a aplicação de Múltiplos Links de Autenticação a Contas Firebase.

Abaixo os vídeos de implementação da correção de alguns bugs do projeto e implementação da funcionalidade de link de logins.

Segue links dos posts já liberados dessa série:

GitHub Login, Firebase Android - Parte 9

Recuperação de Senha, Firebase Atualizado - Parte 8

Twitter Login (Fabric), Firebase Android - Parte 7

Google SignIn API, Firebase Android - Parte 6

Facebook Login, Firebase Android - Parte 5

Remoção de Conta e Dados de Login, Firebase Android - Parte 4

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

Eventos de Leitura e Firebase UI Android - Parte 2

Persistência Com Firebase Android - Parte 1

Referência:

Link Multiple Auth Providers to an Account on Android

Vlw.

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Relacionado

Padrões de Implementação - Um Catálogo de Padrões Indispensável Para o Dia a Dia do ProgramadorPadrões de Implementação - Um Catálogo de Padrões Indispensável Para o Dia a Dia do ProgramadorLivros
Refatoração Para PadrõesRefatoração Para PadrõesLivros
Refatoração de Código: Extrair ParâmetroRefatoração de Código: Extrair ParâmetroAndroid
Refatoração de Código: Substituir Código de Tipo Por ClasseRefatoração de Código: Substituir Código de Tipo Por ClasseAndroid

Compartilhar

Comentários Facebook

Comentários Blog (2)

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...
Johnathan (1) (0)
07/06/2016
Thiengo tudo bem, você sabe algo sobra esse novo layout (Constraint layout) e como deve se usar , pelo o que eu seu ele parece bem interessante e ótima serie  sobre Firebase ,Obrigado!!
Responder
Vinícius Thiengo (2) (0)
07/06/2016
Fala Johnathan, blz?
Tomei ciencia agora com seu comentário (http://tools.android.com/tech-docs/layout-editor ), parece ser bem interessante... um possível vídeo. Obg. Abraço
Responder