In questa lezione, vedremo come integrare il social login tramite Google utilizzando Firebase nella nostra applicazione. I passi da compiere sono molto simili a quelli illustrati nella lezione 7, a differenza dell'implementazione dei metodi di login e logout.
Modifica del build.gradle del modulo app
Apriamo il build.gradle del Module:app e nelle dipendenze dell'applicazione aggiungiamo quella mancante per Google:
dependencies {
. . .
//Google Play Services
compile 'com.google.android.gms:play-services-auth:11.8.0'
}
Sincronizziamo il gradle cliccando su Sync Now, che comparirà in alto a destra dopo aver aggiunto la dipendenza.
Modifica delle risorse e dell'AndroidManifest
Apriamo l'AndroidManifest.xml per aggiungere all'interno del tag application
i meta-data relativi alla versione della libreria Play Services con cui viene compilata l'applicazione:
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
Modifica del layout
Passiamo adesso alla modifica del layout per aggiungere il bottone che permetterà all'utente di effettuare il login. Per farlo, apriamo il file activity_login.xml che si trova in src/main/res/layout, e inseriamo all'interno del ConstaintLayout
il codice seguente:
<com.google.android.gms.common.SignInButton
android:id="@+id/bt_log_in"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:visibility="visible"
tools:visibility="gone" />
In questo modo abbiamo aggiunto il SignInButton
fornito da Google, posizionandolo al centro del parent.
Modifica della LoginActivity
Inseriti gli elementi basilari per la schermata di login, è necessario configurarne la logica per eseguire il processo di autorizzazione e autenticazione.
All'interno dell'Activity
dovremo compiere diverse operazioni che si riassumono nei seguenti passi:
- implementare l'interfaccia
OnClickListener
; - inizializzare l'autenticazione tramite Firebase e Google implementando il metodo
onActivityResult
; - implementare i metodi di login e logout;
- gestire l'access token tramite Firebase;
- aggiornare l'interfaccia in base all'esito dell'autenticazione;
Iniziamo con l'instanziare il bottone di login che utilizzeremo nella nostra Activity
.
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
private SignInButton mLoginButton;
. . .
@Override
protected void onCreate(Bundle savedInstanceState) {
. . .
mLoginButton = findViewById(R.id.bt_log_in);
mLoginButton.setOnClickListener(this);
mLoginButton.setSize(SignInButton.SIZE_WIDE);
findViewById(R.id.bt_log_out).setOnClickListener(this);
. . .
}
. . .
public void signInt() { /*@TODO*/ }
//modificato rispetto la lezione 15
@Override
public void onClick(View v) {
int i = v.getId();
if (i == R.id.bt_log_in) {
signIn();
} else if (i == R.id.bt_log_out) {
signOut();
}
}
}
Nel codice proposto, è stata impostata la dimensione del bottone di login di Google a SIZE_WIDE
, al fine di avere un bottone sufficientemente grande. Inoltre, i metodi di signIn
e signOut
verranno invocati quando l'utente cliccherà sul bottone di logout.
Focalizziamoci ora sull'inizializzazione del processo di login. All'interno del metodo OnCreate
, istanziamo la FirebaseAuth
, le GoogleSignInOptions
e il GoogleSignInClient
, che sfruttando le opzioni definite gestirà il processo di login/logout. Per farlo definiamo le variabili:
private FirebaseAuth mFirebaseAuth;
private GoogleSignInClient mGoogleSignInClient;
e nel metodo onCreate
:
protected void onCreate(Bundle savedInstanceState) {
. . .
mFirebaseAuth = FirebaseAuth.getInstance();
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build();
mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
}
In questo modo abbiamo:
- ottenuto un'istanza di
FirebaseAuth
; - definito le opzioni per il login tramite Google:
- impostando il tipo di login (
DEFAULT_SIGN_IN
); - specificando che è richiesto un token ID per gli utenti autenticati definendo l'ID client del server che verificherà l'integrità del token;
- specificando che è richiesta l'email per l'autenticazione;
- impostando il tipo di login (
- creato una nuova istanza del
GoogleSignInClient
per il login.
Per completare l'integrazione del login, aggiungiamo la definizione dei metodi signIn
e signOut
che avvieranno rispettivamente l'Intent di login e il processo di logout da Firebase e da Google, come mostrato di seguito:
private static final int RC_SIGN_IN = 9001;
. . .
private void signIn() {
Intent signInIntent = mGoogleSignInClient.getSignInIntent();
startActivityForResult(signInIntent, RC_SIGN_IN);
}
private void signOut() {
mFirebaseAuth.signOut();
mGoogleSignInClient.signOut().addOnCompleteListener(this,
new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
updateUI(null);
}
});
}
La variabile RC_SIGN_IN
è il request code per il login di Google e serve come identificativo per il metodo onActivityResult
che eseguirà il flusso di operazioni previsto per quel requestCode
.
Implementiamo, quindi, il metodo onActivityResult
che inoltra al metodo firebaseAuthWithGoogle
l'account ottenuto durante il processo di login avviato tramite l'Intent.
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
try {
GoogleSignInAccount account = task.getResult(ApiException.class);
firebaseAuthWithGoogle(account);
} catch (ApiException e) {
updateUI(null);
}
}
}
Implementiamo ora i metodi firebaseAuthWithGoogle
e updateUI
per gestire il GoogleSignInAccount
ottenuto tramite Google durante il processo di login e per aggiornare l'interfaccia utente in base all'esito del login.
Il firebaseAuthWithGoogle
va implementato come segue.
private void firebaseAuthWithGoogle(GoogleSignInAccount account) {
showProgressDialog();
AuthCredential credential = GoogleAuthProvider.getCredential(account.getIdToken(), null);
mFirebaseAuth.signInWithCredential(credential)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
FirebaseUser user = mFirebaseAuth.getCurrentUser();
updateUI(user);
} else {
Toast.makeText(LoginActivity.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
updateUI(null);
}
hideProgressDialog();
}
});
}
All'interno del metodo, si è:
- richiamato il metodo per mostrare la
ProgressDialog
; - ottenuto le credenziali attraverso il metodo statico
getCredential
dell'oggettoGoogleAuthProvider
che prende in input l'id del token (ottenuto durante l'autenticazione e che deve essere verificato dal server) e restituisce un oggettoAuthCredential
; - tentato l'accesso a Google tramite il metodo
signInWithCredential
passando in input le credenziali appena recuperate restituendo un oggettoTask<AuthResult>
; - aggiunto un
listener
alTask
che viene richiamato quando quest'ultimo sarà terminato; - richiamato il metodo per nascondere la
ProgressDialog
.
In particolare, quando il Task
sarà terminato, in caso di successo verranno recuperate le informazioni dell'utente e aggiornata l'interfaccia tramite il metodo updateUI
.
Implementiamo, ora, il metodo updateUI
che verificherà se l'oggetto FirebaseUser
è stato inizializzato aggiornando l'interfaccia. In caso di successo, verrà nascosto il bottone di login rendendo visibile e popolando il LinearLayout
definito in precedenza. Contrariamente, verrà mostrato solo il bottone di login. Si riporta per completezza l'implementazione del metodo:
private void updateUI(FirebaseUser user) {
if (user != null) {
mLoginButton.setVisibility(View.GONE);
mUserInfoLayout.setVisibility(View.VISIBLE);
mUserNameTV.setText(user.getDisplayName());
String imageUrl = user.getPhotoUrl().toString();
imageUrl = imageUrl.replace("/s96-c/","/s200-c/");
String userId = "";
for(UserInfo profile : user.getProviderData()){
if(FacebookAuthProvider.PROVIDER_ID.equals(profile.getProviderId())) {
userId = profile.getUid();
}
}
Picasso.with(this)
.load(imageUrl)
.placeholder(R.drawable.placeholder)
.into(mUserImageIV);
} else {
mLoginButton.setVisibility(View.VISIBLE);
mUserInfoLayout.setVisibility(View.GONE);
}
}
In particolare, per popolare il layout con le informazioni dell'utente è possibile utilizzare l'oggetto FirebaseUser
da cui estrarre i dati dell'utente.
L'URL dell'immagine di profilo può essere ottenuta tramite il metodo getPhotoUrl().toString()
dell'oggetto, ma questa soluzione limita la dimensione dell'immagine a quella più piccola offerta dalle API di Google. Questa limitazione è facilmente superabile effettuando una sostituzione all'interno dell'URL in formato stringa. In particolare, impostando la dimensione a s200-c
otterremo un'immagine di profilo quadrata dalle dimensioni di 200x200 pixel.
Infine, la responsabilità di caricare l'immagine verrà assegnata alla libreria Picasso che prende in input l'URL da caricare e il contenitore in cui inserirla.
Risultato finale
Eseguiamo l'applicazione sul dispositivo mobile o sull'emulatore.
All'avvio dell'applicazione, viene mostrata la schermata iniziale contenente il bottone di default precedentemente aggiunto all'interfaccia e qui di seguito riportato.
Cliccando sul bottone verrà richiesto all'utente di autenticarsi con il proprio account Google, chiedendo allo stesso l'autorizzazione per l'accesso ai dati personali da parte dell'applicazione.
Una volta loggati, verremo nuovamente reindirizzati alla schermata della LoginActivity
ma, al posto del bottone di login, verranno mostrate le informazioni dell'utente e il bottone di logout.
Cliccando sul bottone di logout verrà effettuato il logout da Google e l'applicazione mostrerà nuovamente il bottone di login.