1. Prima di iniziare
In questo codelab imparerai a implementare Accedi con Google su Android utilizzando Credential Manager.
Prerequisiti
- Conoscenza di base dell'utilizzo di Kotlin per lo sviluppo Android
- Conoscenza di base di Jetpack Compose (ulteriori informazioni sono disponibili qui)
Obiettivi didattici
- Come creare un progetto Google Cloud
- Come creare client OAuth nella console Google Cloud
- Come implementare Accedi con Google utilizzando il flusso del foglio inferiore
- Come implementare Accedi con Google utilizzando il flusso del pulsante
Cosa serve
- Android Studio (scarica qui)
- Un computer che soddisfi i requisiti di sistema di Android Studio
- Un computer che soddisfi i requisiti di sistema di Android Emulator
2. Creare un progetto Android Studio
Durata 3:00 - 5:00
Per iniziare, dobbiamo creare un nuovo progetto in Android Studio:
- Apri Android Studio
- Fai clic su Nuovo progetto
.
- Seleziona Smartphone e tablet e Attività vuota
- Fai clic su Avanti.
- Ora è il momento di configurare alcuni elementi del progetto:
- Nome: il nome del progetto
- Nome pacchetto: questo campo verrà compilato automaticamente in base al nome del progetto
- Salva in: per impostazione predefinita, dovrebbe essere la cartella in cui Android Studio salva i progetti. Puoi modificarla come preferisci.
- SDK minimo: la versione più bassa dell'SDK Android su cui è progettata per essere eseguita la tua app. In questo Code Lab utilizzeremo l'API 36 (Baklava).
- Fai clic su Fine.
- Android Studio creerà il progetto e scaricherà le dipendenze necessarie per l'applicazione di base. Questa operazione può richiedere diversi minuti. Per vedere cosa succede, fai clic sull'icona della build:
- Al termine, Android Studio dovrebbe avere un aspetto simile a questo:
3. Configurare il progetto Google Cloud.
Crea un progetto Google Cloud
- Vai alla console Google Cloud.
- Apri il tuo progetto o creane uno nuovo
- Fai clic su API e servizi
- Vai alla schermata per il consenso OAuth.
- Per continuare, devi compilare i campi in Panoramica. Fai clic su Inizia per iniziare a compilare queste informazioni:
- Nome app: il nome di questa app, che deve essere lo stesso che hai utilizzato durante la creazione del progetto in Android Studio
- Email di assistenza utenti: verrà visualizzato l'Account Google con cui accedi e tutti i Gruppi Google che gestisci
- Pubblico:
- Interna per un'app utilizzata solo all'interno della tua organizzazione. Se non hai un'organizzazione associata al progetto Google Cloud, non potrai selezionarla.
- Esterno sarà quello che utilizzeremo.
- Dati di contatto: può essere qualsiasi email che vuoi che sia il punto di contatto per la richiesta
- Leggi le Norme sui dati utente: servizi API di Google.
- Dopo aver esaminato e accettato le Norme relative ai dati utente, fai clic su Crea
Configurare i client OAuth
Ora che abbiamo configurato un progetto Google Cloud, dobbiamo aggiungere un client web e un client Android in modo da poter effettuare chiamate API al server backend OAuth con i relativi ID client.
Per il client web Android, devi disporre di:
- Il nome del pacchetto dell'app (ad es. com.example.example)
- Firma SHA-1 della tua app
- Che cos'è una firma SHA-1?
- L'impronta SHA-1 è un hash crittografico generato dalla chiave di firma della tua app. Funge da identificatore univoco per il certificato di firma della tua app specifica. Consideralo come una "firma" digitale per la tua app.
- Perché abbiamo bisogno della firma SHA-1?
- L'impronta SHA-1 garantisce che solo la tua app, firmata con la tua chiave di firma specifica, possa richiedere token di accesso utilizzando il tuo ID client OAuth 2.0, impedendo ad altre app (anche quelle con lo stesso nome del pacchetto) di accedere alle risorse e ai dati utente del tuo progetto.
- Ecco un esempio:
- La chiave di firma dell'app è come la chiave fisica della "porta " dell'app. È ciò che consente l'accesso al funzionamento interno dell'app.
- L'impronta SHA-1 è come un ID badge univoco collegato alla tua chiave fisica. È un codice specifico che identifica quella chiave in particolare.
- L'ID client OAuth 2.0 è come un codice di accesso a una risorsa o a un servizio Google specifico (ad es. Accedi con Google).
- Quando fornisci l'impronta SHA-1 durante la configurazione del client OAuth, stai essenzialmente dicendo a Google: "Solo la chiave con questo ID specifico (SHA-1) può aprire questo codice di accesso (ID client)". In questo modo, solo la tua app può accedere ai servizi Google collegati a quel codice di accesso."
- Che cos'è una firma SHA-1?
Per il client web, è sufficiente il nome che vuoi utilizzare per identificare il client nella console.
Crea un client OAuth 2.0 per Android
- Vai alla pagina Clienti
- Fai clic su Crea client.
- Seleziona Android per il tipo di applicazione.
- Dovrai specificare il nome del pacchetto della tua app
- Da Android Studio dobbiamo recuperare la firma SHA-1 della nostra app e copiarla/incollarla qui:
- Vai ad Android Studio e apri il terminale.
- Esegui questo comando:
Questo comando è progettato per elencare i dettagli di una voce specifica (alias) all'interno di un keystore.keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
-list
: questa opzione indica a keytool di elencare i contenuti del keystore.-v
: questa opzione attiva l'output dettagliato, fornendo informazioni più dettagliate sulla voce.-keystore ~/.android/debug.keystore
: specifica il percorso del file del keystore.-alias androiddebugkey
: specifica l'alias (nome della voce) della chiave che vuoi esaminare.-storepass android
: fornisce la password per il file del keystore.-keypass android
: fornisce la password per la chiave privata dell'alias specificato.
- Copia il valore della firma SHA-1:
- Torna alla finestra di Google Cloud e incolla il valore della firma SHA-1:
- La schermata dovrebbe ora essere simile a questa e puoi fare clic su Crea:
Crea un client web OAuth 2.0
- Per creare un ID client applicazione web, ripeti i passaggi 1-2 della sezione Crea client Android e seleziona Applicazione web per il tipo di applicazione.
- Assegna un nome al client (questo sarà il client OAuth):
- Fai clic su Crea
.
- Copia l'ID client dalla finestra popup, ti servirà in seguito
Ora che abbiamo configurato tutti i client OAuth, possiamo tornare ad Android Studio e creare la nostra app per Android Accedi con Google.
4. Configurare Android Virtual Device
Per testare rapidamente la tua applicazione senza un dispositivo Android fisico, devi creare un dispositivo virtuale Android su cui puoi compilare ed eseguire immediatamente la tua app da Android Studio. Se vuoi eseguire test con un dispositivo Android fisico, puoi seguire le istruzioni riportate nella documentazione per gli sviluppatori Android.
Crea un dispositivo virtuale Android
- In Android Studio, apri Device Manager
.
- Fai clic sul pulsante + > Crea dispositivo virtuale
- Da qui puoi aggiungere qualsiasi dispositivo di cui hai bisogno per il tuo progetto. Ai fini di questo codelab, seleziona Medium Phone e poi fai clic su Avanti
- Ora puoi configurare il dispositivo per il tuo progetto assegnandogli un nome univoco, scegliendo la versione di Android che verrà eseguita sul dispositivo e altro ancora. Assicurati che l'API sia impostata su API 36 "Baklava"; Android 16, quindi fai clic su Fine
- Dovresti vedere il nuovo dispositivo in Device Manager. Per verificare che il dispositivo funzioni, fai clic su
accanto al dispositivo che hai appena creato
.
- Il dispositivo dovrebbe essere in esecuzione.
Accedere all'Android Virtual Device
Il dispositivo che hai appena creato funziona. Ora, per evitare errori durante il test di Accedi con Google, dovrai accedere al dispositivo con un Account Google.
- Vai a Impostazioni:
- Fai clic al centro dello schermo sul dispositivo virtuale e scorri verso l'alto.
- Cerca l'app Impostazioni e fai clic
- Fai clic su Google in Impostazioni
.
- Fai clic su Accedi e segui le istruzioni per accedere al tuo Account Google
.
- Ora dovresti aver eseguito l'accesso sul dispositivo
Il tuo dispositivo Android virtuale è ora pronto per i test.
5. Aggiungere dipendenze
Durata 5:00
Per effettuare chiamate API OAuth, dobbiamo prima integrare le librerie necessarie che ci consentono di effettuare richieste di autenticazione e utilizzare gli ID Google per effettuare queste richieste:
- libs.googleid
- libs.play.services.auth
- Vai a File > Project Structure (Struttura progetto):
- Poi vai a Dipendenze > app > "+" > Dipendenza libreria
- Ora dobbiamo aggiungere le nostre librerie:
- Nella finestra di dialogo di ricerca, digita googleid e fai clic su Cerca.
- Dovrebbe esserci una sola voce. Selezionala e scegli la versione più recente disponibile (al momento di questo Codelab è la 1.1.1).
- Fai clic su Ok.
- Ripeti i passaggi 1-3, ma cerca "play-services-auth" e seleziona la riga con "com.google.android.gms" come ID gruppo e "play-services-auth" come Nome artefatto
- Fai clic su Ok.
6. Flusso del riquadro inferiore
Il flusso del foglio inferiore sfrutta l'API Credential Manager per consentire agli utenti di accedere alla tua app utilizzando i propri Account Google su Android in modo più semplice. È progettato per essere rapido e pratico, soprattutto per gli utenti di ritorno. Questo flusso deve essere attivato all'avvio dell'app.
Crea la richiesta di accesso
- Per iniziare, rimuovi le funzioni
Greeting()
eGreetingPreview()
daMainActivity.kt
, non ci serviranno. - Ora dobbiamo assicurarci che i pacchetti necessari vengano importati per questo progetto. Aggiungi le seguenti istruzioni
import
dopo quelle esistenti a partire dalla riga 3:import android.content.ContentValues.TAG import android.content.Context import android.credentials.GetCredentialException import android.os.Build import android.util.Log import android.widget.Toast import androidx.annotation.RequiresApi import androidx.compose.foundation.clickable import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.credentials.CredentialManager import androidx.credentials.exceptions.GetCredentialCancellationException import androidx.credentials.exceptions.GetCredentialCustomException import androidx.credentials.exceptions.NoCredentialException import androidx.credentials.GetCredentialRequest import com.google.android.libraries.identity.googleid.GetGoogleIdOption import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException import java.security.SecureRandom import java.util.Base64 import kotlinx.coroutines.CoroutineScope import androidx.compose.runtime.LaunchedEffect import kotlinx.coroutines.delay import kotlinx.coroutines.launch
- Successivamente, dobbiamo creare la funzione per creare la richiesta del foglio inferiore. Incolla questo codice sotto la classe MainActivity
//This line is not needed for the project to build, but you will see errors if it is not present.
//This code will not work on Android versions < UpsideDownCake
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun BottomSheet(webClientId: String) {
val context = LocalContext.current
// LaunchedEffect is used to run a suspend function when the composable is first launched.
LaunchedEffect(Unit) {
// Create a Google ID option with filtering by authorized accounts enabled.
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(true)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
// Create a credential request with the Google ID option.
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()
// Attempt to sign in with the created request using an authorized account
val e = signIn(request, context)
// If the sign-in fails with NoCredentialException, there are no authorized accounts.
// In this case, we attempt to sign in again with filtering disabled.
if (e is NoCredentialException) {
val googleIdOptionFalse: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(false)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val requestFalse: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOptionFalse)
.build()
//We will build out this function in a moment
signIn(requestFalse, context)
}
}
}
//This function is used to generate a secure nonce to pass in with our request
fun generateSecureRandomNonce(byteLength: Int = 32): String {
val randomBytes = ByteArray(byteLength)
SecureRandom.getInstanceStrong().nextBytes(randomBytes)
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
}
Analizziamo cosa fa questo codice:
fun BottomSheet(webClientId: String) {...}
: crea una funzione chiamata BottomSheet che accetta un argomento stringa chiamato webClientid
val context = LocalContext.current
: recupera il contesto Android corrente. È necessario per varie operazioni, tra cui l'avvio dei componenti dell'interfaccia utente.LaunchedEffect(Unit) { ... }
:LaunchedEffect
è un composable Jetpack Compose che ti consente di eseguire una funzione di sospensione (una funzione che può mettere in pausa e riprendere l'esecuzione) all'interno del ciclo di vita del composable. L'unità come chiave indica che questo effetto verrà eseguito una sola volta al primo avvio del composable.val googleIdOption: GetGoogleIdOption = ...
: crea un oggettoGetGoogleIdOption
. Questo oggetto configura il tipo di credenziale richiesta a Google..Builder()
: per configurare le opzioni viene utilizzato un pattern di creazione..setFilterByAuthorizedAccounts(true)
: specifica se consentire all'utente di selezionare tra tutti gli Account Google o solo quelli che hanno già autorizzato l'app. In questo caso, è impostato su true, il che significa che la richiesta verrà effettuata utilizzando le credenziali che l'utente ha precedentemente autorizzato per l'utilizzo con questa app, se disponibili..setServerClientId(webClientId)
: imposta l'ID client server, che è un identificatore univoco per il backend dell'app. È necessario per ottenere un token ID..setNonce(generateSecureRandomNonce())
: imposta un nonce, un valore casuale, per impedire attacchi di replay e garantire che il token ID sia associato alla richiesta specifica..build()
: crea l'oggettoGetGoogleIdOption
con la configurazione specificata.
val request: GetCredentialRequest = ...
: crea un oggettoGetCredentialRequest
. Questo oggetto incapsula l'intera richiesta di credenziali..Builder()
: Avvia il pattern di creazione per configurare la richiesta..addCredentialOption(googleIdOption)
: aggiunge googleIdOption alla richiesta, specificando che vogliamo richiedere un token ID Google..build()
: crea l'oggettoGetCredentialRequest
.
val e = signIn(request, context)
: tenta di accedere per l'utente con la richiesta creata e il contesto attuale. Il risultato della funzione signIn viene memorizzato in e. Questa variabile conterrà il risultato riuscito o un'eccezione.if (e is NoCredentialException) { ... }
: Questo è un controllo condizionale. Se la funzione signIn non va a buon fine e viene generata un'eccezione NoCredentialException, significa che non sono disponibili account autorizzati in precedenza.val googleIdOptionFalse: GetGoogleIdOption = ...
: se il precedentesignIn
non è riuscito, questa parte crea un nuovoGetGoogleIdOption
..setFilterByAuthorizedAccounts(false)
: questa è la differenza fondamentale rispetto alla prima opzione. Disattiva il filtro degli account autorizzati, il che significa che per accedere può essere utilizzato qualsiasi Account Google sul dispositivo.val requestFalse: GetCredentialRequest = ...
: viene creato un nuovoGetCredentialRequest
congoogleIdOptionFalse
.signIn(requestFalse, context)
: viene eseguito il tentativo di accesso dell'utente con la nuova richiesta che consente l'utilizzo di qualsiasi account.
In sostanza, questo codice prepara una richiesta all'API Credential Manager per recuperare un token ID Google per l'utente, utilizzando le configurazioni fornite. GetCredentialRequest può quindi essere utilizzato per avviare la UI di gestione delle credenziali, in cui l'utente può selezionare il proprio Account Google e concedere le autorizzazioni necessarie.
fun generateSecureRandomNonce(byteLength: Int = 32): String
: Definisce una funzione denominata generateSecureRandomNonce
. Accetta un argomento intero byteLength (con un valore predefinito di 32) che specifica la lunghezza desiderata del nonce in byte. Restituisce una stringa, che sarà la rappresentazione con codifica Base64 dei byte casuali.
val randomBytes = ByteArray(byteLength)
: crea un array di byte della lunghezza specificata per contenere i byte casuali.SecureRandom.getInstanceStrong().nextBytes(randomBytes)
:SecureRandom.getInstanceStrong()
: in questo modo si ottiene un generatore di numeri casuali crittograficamente sicuro. Ciò è fondamentale per la sicurezza, in quanto garantisce che i numeri generati siano veramente casuali e non prevedibili. Utilizza la fonte di entropia più forte disponibile sul sistema..nextBytes(randomBytes)
: questo campo compila l'array randomBytes con byte casuali generati dall'istanza SecureRandom.
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
:Base64.getUrlEncoder()
: recupera un codificatore Base64 che utilizza un alfabeto con protezione dell'URL (utilizzando - e _ anziché + e /). Questo è importante perché garantisce che la stringa risultante possa essere utilizzata in modo sicuro negli URL senza richiedere un'ulteriore codifica..withoutPadding()
: vengono rimossi tutti i caratteri di riempimento dalla stringa codificata in Base64. Spesso è preferibile per rendere il nonce leggermente più corto e compatto..encodeToString(randomBytes)
: codifica randomBytes in una stringa Base64 e la restituisce.
In sintesi, questa funzione genera un nonce casuale crittograficamente sicuro di una lunghezza specificata, lo codifica utilizzando Base64 sicuro per gli URL e restituisce la stringa risultante. Si tratta di una pratica standard per generare nonce sicuri da utilizzare in contesti sensibili alla sicurezza.
Inviare la richiesta di accesso
Ora che siamo in grado di creare la nostra richiesta di accesso, possiamo utilizzare Credential Manager per utilizzarla per l'accesso. Per farlo, dobbiamo creare una funzione che gestisca il passaggio delle richieste di accesso utilizzando Credential Manager, gestendo al contempo le eccezioni comuni che potremmo incontrare.
Per farlo, puoi incollare questa funzione sotto la funzione BottomSheet()
.
//This code will not work on Android versions < UPSIDE_DOWN_CAKE when GetCredentialException is
//is thrown.
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
suspend fun signIn(request: GetCredentialRequest, context: Context): Exception? {
val credentialManager = CredentialManager.create(context)
val failureMessage = "Sign in failed!"
var e: Exception? = null
//using delay() here helps prevent NoCredentialException when the BottomSheet Flow is triggered
//on the initial running of our app
delay(250)
try {
// The getCredential is called to request a credential from Credential Manager.
val result = credentialManager.getCredential(
request = request,
context = context,
)
Log.i(TAG, result.toString())
Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
Log.i(TAG, "(☞゚ヮ゚)☞ Sign in Successful! ☜(゚ヮ゚☜)")
} catch (e: GetCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Failure getting credentials", e)
} catch (e: GoogleIdTokenParsingException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with parsing received GoogleIdToken", e)
} catch (e: NoCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": No credentials found", e)
return e
} catch (e: GetCredentialCustomException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with custom credential request", e)
} catch (e: GetCredentialCancellationException) {
Toast.makeText(context, ": Sign-in cancelled", Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Sign-in was cancelled", e)
}
return e
}
Vediamo ora cosa fa il codice:
suspend fun signIn(request: GetCredentialRequest, context: Context): Exception?
: Definisce una funzione di sospensione denominata signIn. Ciò significa che può essere messo in pausa e ripreso senza bloccare il thread principale.Restituisce un Exception?
che sarà nullo se l'accesso va a buon fine o l'eccezione specifica se l'accesso non riesce.
Richiede due parametri:
request
: un oggettoGetCredentialRequest
, che contiene la configurazione per il tipo di credenziali da recuperare (ad es. ID Google).context
: il contesto Android necessario per interagire con il sistema.
Per il corpo della funzione:
val credentialManager = CredentialManager.create(context)
: crea un'istanza di CredentialManager, che è l'interfaccia principale per interagire con l'API Credential Manager. In questo modo l'app avvierà il flusso di accesso.val failureMessage = "Sign in failed!"
: definisce una stringa (failureMessage) da visualizzare in un avviso popup quando l'accesso non riesce.var e: Exception? = null
: questa riga inizializza una variabile e per archiviare qualsiasi eccezione che potrebbe verificarsi durante il processo, a partire da null.delay(250)
: introduce un ritardo di 250 millisecondi. Si tratta di una soluzione alternativa a un potenziale problema per cui NoCredentialException potrebbe essere generata immediatamente all'avvio dell'app, soprattutto quando si utilizza un flusso BottomSheet. In questo modo, il sistema ha il tempo di inizializzare Gestore delle credenziali.try { ... } catch (e: Exception) { ... }
:Un blocco try-catch viene utilizzato per una gestione degli errori efficace. In questo modo, se si verifica un errore durante la procedura di accesso, l'app non si arresta in modo anomalo e può gestire l'eccezione in modo controllato.val result = credentialManager.getCredential(request = request, context = context)
: qui viene effettuata la chiamata effettiva all'API Credential Manager e viene avviato il processo di recupero delle credenziali. Prende la richiesta e il contesto come input e presenta un'interfaccia utente per consentire all'utente di selezionare una credenziale. In caso di esito positivo, restituirà un risultato contenente la credenziale selezionata. Il risultato di questa operazione,GetCredentialResponse
, viene memorizzato nella variabileresult
.Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
:mostra un breve messaggio di notifica che indica che l'accesso è stato eseguito correttamente.Log.i(TAG, "Sign in Successful!")
: registra un messaggio divertente e riuscito in logcat.catch (e: GetCredentialException)
: Gestisce le eccezioni di tipoGetCredentialException
. Si tratta di una classe principale per diverse eccezioni specifiche che possono verificarsi durante il processo di recupero delle credenziali.catch (e: GoogleIdTokenParsingException)
: gestisce le eccezioni che si verificano quando si verifica un errore durante l'analisi del token ID Google.catch (e: NoCredentialException)
: gestisceNoCredentialException
, che viene generata quando non sono disponibili credenziali per l'utente (ad esempio, non ne ha salvate o non ha un Account Google).- Fondamentalmente, questa funzione restituisce l'eccezione archiviata in
e
,NoCredentialException
, consentendo al chiamante di gestire il caso specifico se non sono disponibili credenziali.
- Fondamentalmente, questa funzione restituisce l'eccezione archiviata in
catch (e: GetCredentialCustomException)
: gestisce le eccezioni personalizzate che potrebbero essere generate dal fornitore di credenziali.catch (e: GetCredentialCancellationException)
: gestisceGetCredentialCancellationException
, che viene generata quando l'utente annulla la procedura di accesso.Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
: mostra un messaggio popup che indica che l'accesso non è riuscito utilizzando failureMessage.Log.e(TAG, "", e)
: registra l'eccezione in Android logcat utilizzando Log.e, che viene utilizzato per gli errori. Verrà inclusa la stacktrace dell'eccezione per facilitare il debug. Include anche l'emoticon arrabbiata per divertimento.
return e
: la funzione restituisce l'eccezione, se ne è stata rilevata una, o null se l'accesso è andato a buon fine.
In sintesi, questo codice fornisce un modo per gestire l'accesso degli utenti utilizzando l'API Credential Manager, gestisce l'operazione asincrona, gestisce i potenziali errori e fornisce feedback all'utente tramite toast e log, aggiungendo un tocco di umorismo alla gestione degli errori.
Implementare il flusso del riquadro inferiore nell'app
Ora possiamo impostare una chiamata per attivare il flusso BottomSheet nella nostra classe MainActivity
utilizzando il seguente codice e il nostro ID client dell'applicazione web che abbiamo copiato in precedenza da Google Cloud Console:
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//replace with your own web client ID from Google Cloud Console
val webClientId = "YOUR_CLIENT_ID_HERE"
setContent {
//ExampleTheme - this is derived from the name of the project not any added library
//e.g. if this project was named "Testing" it would be generated as TestingTheme
ExampleTheme {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
) {
//This will trigger on launch
BottomSheet(webClientId)
}
}
}
}
}
Ora possiamo salvare il progetto (File > Salva) ed eseguirlo:
- Premi il pulsante Esegui:
- Una volta eseguita l'app sull'emulatore, dovrebbe essere visualizzato il BottomSheet di accesso. Fai clic su Continua per testare l'accesso
- Dovrebbe essere visualizzato un messaggio di notifica che indica che l'accesso è stato eseguito correttamente.
7. Flusso dei pulsanti
Il flusso dei pulsanti per Accedi con Google consente agli utenti di registrarsi o accedere più facilmente alla tua app per Android utilizzando il proprio Account Google esistente. Lo toccheranno se chiudono il foglio inferiore o preferiscono utilizzare esplicitamente il proprio Account Google per l'accesso o la registrazione. Per gli sviluppatori, significa un onboarding più semplice e meno difficoltà durante la registrazione.
Anche se è possibile farlo con un pulsante Jetpack Compose predefinito, utilizzeremo un'icona del brand pre-approvata dalla pagina Linee guida per il branding di Accedi con Google.
Aggiungere l'icona del brand al progetto
- Scarica il file ZIP delle icone del brand preapprovate qui.
- Decomprimi il file signin-assest.zip dai download (questa operazione varia in base al sistema operativo del computer). Ora puoi aprire la cartella signin-assets ed esaminare le icone disponibili. Per questo codelab, utilizzeremo
signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png
. - Copia il file
- Incolla nel progetto in Android Studio in res > drawable facendo clic con il tasto destro del mouse sulla cartella drawable e facendo clic su Incolla (potresti dover espandere la cartella res per visualizzarla).
- Viene visualizzata una finestra di dialogo che ti chiede di rinominare il file e di confermare la directory in cui verrà aggiunto. Rinomina l'asset in siwg_button.png, quindi fai clic su Ok
.
Codice del flusso del pulsante
Questo codice utilizzerà la stessa funzione signIn()
utilizzata per BottomSheet()
, ma utilizzerà GetSignInWithGoogleOption
anziché GetGoogleIdOption
, poiché questo flusso non sfrutta le credenziali e le passkey memorizzate sul dispositivo per mostrare le opzioni di accesso. Ecco il codice che puoi incollare sotto la funzione BottomSheet()
:
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun ButtonUI(webClientId: String) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val onClick: () -> Unit = {
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption
.Builder(serverClientId = webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(signInWithGoogleOption)
.build()
coroutineScope.launch {
signIn(request, context)
}
}
Image(
painter = painterResource(id = R.drawable.siwg_button),
contentDescription = "",
modifier = Modifier
.fillMaxSize()
.clickable(enabled = true, onClick = onClick)
)
}
Per capire cosa fa il codice:
fun ButtonUI(webClientId: String)
: dichiara una funzione denominata ButtonUI
che accetta un webClientId
(l'ID client del tuo progetto Google Cloud) come argomento.
val context = LocalContext.current
: recupera il contesto Android corrente. È necessario per varie operazioni, tra cui l'avvio dei componenti dell'interfaccia utente.
val coroutineScope = rememberCoroutineScope()
: crea un ambito di coroutine. Viene utilizzato per gestire le attività asincrone, consentendo l'esecuzione del codice senza bloccare il thread principale. rememberCoroutineScope
() è una funzione componibile di Jetpack Compose che fornisce un ambito legato al ciclo di vita del componibile.
val onClick: () -> Unit = { ... }
: crea una funzione lambda che verrà eseguita quando viene fatto clic sul pulsante. La funzione Lambda:
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(serverClientId = webClientId).setNonce(generateSecureRandomNonce()).build()
: questa parte crea un oggettoGetSignInWithGoogleOption
. Questo oggetto viene utilizzato per specificare i parametri per la procedura "Accedi con Google", richiedewebClientId
e un nonce (una stringa casuale utilizzata per la sicurezza).val request: GetCredentialRequest = GetCredentialRequest.Builder().addCredentialOption(signInWithGoogleOption).build()
: viene creato un oggettoGetCredentialRequest
. Questa richiesta verrà utilizzata per ottenere le credenziali utente utilizzando Gestione credenziali.GetCredentialRequest
aggiungeGetSignInWithGoogleOption
creato in precedenza come opzione per richiedere una credenziale "Accedi con Google".
coroutineScope.launch { ... }
: unCoroutineScope
per gestire le operazioni asincrone (utilizzando le coroutine).signIn(request, context)
: chiama la funzionesignIn
() definita in precedenza
Image(...)
: viene visualizzata un'immagine utilizzando painterResource
che carica l'immagine R.drawable.siwg_button
Modifier.fillMaxSize().clickable(enabled = true, onClick = onClick)
:fillMaxSize()
: L'immagine riempie lo spazio disponibile.clickable(enabled = true, onClick = onClick)
: rende l'immagine cliccabile e, quando viene selezionata, esegue la funzione lambda onClick definita in precedenza.
In sintesi, questo codice configura un pulsante "Accedi con Google" in una UI Jetpack Compose. Quando si fa clic sul pulsante, viene preparata una richiesta di credenziali per avviare Credential Manager e consentire all'utente di accedere con il proprio Account Google.
Ora devi aggiornare la classe MainActivity per eseguire la funzione ButtonUI()
:
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//replace with your own web client ID from Google Cloud Console
val webClientId = "YOUR_CLIENT_ID_HERE"
setContent {
//ExampleTheme - this is derived from the name of the project not any added library
//e.g. if this project was named "Testing" it would be generated as TestingTheme
ExampleTheme {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
//This will trigger on launch
BottomSheet(webClientId)
//This requires the user to press the button
ButtonUI(webClientId)
}
}
}
}
}
}
Ora possiamo salvare il progetto (File > Salva) ed eseguirlo:
- Premi il pulsante Esegui:
- Una volta eseguita l'app sull'emulatore, dovrebbe essere visualizzato BottomSheet. Fai clic all'esterno per chiuderla.
- Ora dovresti vedere il pulsante che abbiamo creato nell'app. Fai clic per visualizzare la finestra di dialogo di accesso
- Fai clic sul tuo account per accedere.
8. Conclusione
Hai completato questo codelab. Per ulteriori informazioni o assistenza in merito ad Accedi con Google su Android, consulta la sezione Domande frequenti di seguito:
Domande frequenti
- Stackoverflow
- Guida alla risoluzione dei problemi di Gestore delle credenziali Android
- Domande frequenti su Gestore delle credenziali Android
- Centro assistenza per la verifica di app OAuth
Codice completo di MainActivity.kt
Ecco il codice completo di MainActivity.kt per riferimento:
package com.example.example
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.example.example.ui.theme.ExampleTheme
import android.content.ContentValues.TAG
import android.content.Context
import android.credentials.GetCredentialException
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.credentials.CredentialManager
import androidx.credentials.exceptions.GetCredentialCancellationException
import androidx.credentials.exceptions.GetCredentialCustomException
import androidx.credentials.exceptions.NoCredentialException
import androidx.credentials.GetCredentialRequest
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
import java.security.SecureRandom
import java.util.Base64
import kotlinx.coroutines.CoroutineScope
import androidx.compose.runtime.LaunchedEffect
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//replace with your own web client ID from Google Cloud Console
val webClientId = "YOUR_CLIENT_ID_HERE"
setContent {
//ExampleTheme - this is derived from the name of the project not any added library
//e.g. if this project was named "Testing" it would be generated as TestingTheme
ExampleTheme {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
//This will trigger on launch
BottomSheet(webClientId)
//This requires the user to press the button
ButtonUI(webClientId)
}
}
}
}
}
}
//This line is not needed for the project to build, but you will see errors if it is not present.
//This code will not work on Android versions < UpsideDownCake
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun BottomSheet(webClientId: String) {
val context = LocalContext.current
// LaunchedEffect is used to run a suspend function when the composable is first launched.
LaunchedEffect(Unit) {
// Create a Google ID option with filtering by authorized accounts enabled.
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(true)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
// Create a credential request with the Google ID option.
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()
// Attempt to sign in with the created request using an authorized account
val e = signIn(request, context)
// If the sign-in fails with NoCredentialException, there are no authorized accounts.
// In this case, we attempt to sign in again with filtering disabled.
if (e is NoCredentialException) {
val googleIdOptionFalse: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(false)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val requestFalse: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOptionFalse)
.build()
signIn(requestFalse, context)
}
}
}
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun ButtonUI(webClientId: String) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val onClick: () -> Unit = {
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption
.Builder(serverClientId = webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(signInWithGoogleOption)
.build()
signIn(coroutineScope, request, context)
}
Image(
painter = painterResource(id = R.drawable.siwg_button),
contentDescription = "",
modifier = Modifier
.fillMaxSize()
.clickable(enabled = true, onClick = onClick)
)
}
fun generateSecureRandomNonce(byteLength: Int = 32): String {
val randomBytes = ByteArray(byteLength)
SecureRandom.getInstanceStrong().nextBytes(randomBytes)
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
}
//This code will not work on Android versions < UPSIDE_DOWN_CAKE when GetCredentialException is
//is thrown.
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
suspend fun signIn(request: GetCredentialRequest, context: Context): Exception? {
val credentialManager = CredentialManager.create(context)
val failureMessage = "Sign in failed!"
var e: Exception? = null
//using delay() here helps prevent NoCredentialException when the BottomSheet Flow is triggered
//on the initial running of our app
delay(250)
try {
// The getCredential is called to request a credential from Credential Manager.
val result = credentialManager.getCredential(
request = request,
context = context,
)
Log.i(TAG, result.toString())
Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
Log.i(TAG, "(☞゚ヮ゚)☞ Sign in Successful! ☜(゚ヮ゚☜)")
} catch (e: GetCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Failure getting credentials", e)
} catch (e: GoogleIdTokenParsingException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with parsing received GoogleIdToken", e)
} catch (e: NoCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": No credentials found", e)
return e
} catch (e: GetCredentialCustomException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with custom credential request", e)
} catch (e: GetCredentialCancellationException) {
Toast.makeText(context, ": Sign-in cancelled", Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Sign-in was cancelled", e)
}
return e
}