Scopri come implementare Accedi con Google nella tua app per Android

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

2. Creare un progetto Android Studio

Durata 3:00 - 5:00

Per iniziare, dobbiamo creare un nuovo progetto in Android Studio:

  1. Apri Android Studio
  2. Fai clic su Nuovo progettoBenvenuto in Android Studio.
  3. Seleziona Smartphone e tablet e Attività vuotaProgetto Android Studio
  4. Fai clic su Avanti.
  5. 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).
    Progetto di configurazione di Android Studio
  6. Fai clic su Fine.
  7. 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:Creazione di progetti Android Studio
  8. Al termine, Android Studio dovrebbe avere un aspetto simile a questo:Progetto Android Studio creato

3. Configurare il progetto Google Cloud.

Crea un progetto Google Cloud

  1. Vai alla console Google Cloud.
  2. Apri il tuo progetto o creane uno nuovoGoogle Cloud crea nuovo progettoGCP create new project 2GCP create new project 3
  3. Fai clic su API e serviziAPI e servizi GCP
  4. Vai alla schermata per il consenso OAuth.Schermata per il consenso OAuth di Google Cloud
  5. Per continuare, devi compilare i campi in Panoramica. Fai clic su Inizia per iniziare a compilare queste informazioni:Pulsante Inizia di Google Cloud
    • 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
    Informazioni sull'app Google Cloud
    • 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.
    Pubblico Google Cloud
    • Dati di contatto: può essere qualsiasi email che vuoi che sia il punto di contatto per la richiesta
    Informazioni di contatto GCP
    • Leggi le Norme sui dati utente: servizi API di Google.
  6. Dopo aver esaminato e accettato le Norme relative ai dati utente, fai clic su CreaGCP Create

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."

Per il client web, è sufficiente il nome che vuoi utilizzare per identificare il client nella console.

Crea un client OAuth 2.0 per Android

  1. Vai alla pagina ClientiClient GCP
  2. Fai clic su Crea client.GCP Create Clients
  3. Seleziona Android per il tipo di applicazione.
  4. Dovrai specificare il nome del pacchetto della tua app
  5. Da Android Studio dobbiamo recuperare la firma SHA-1 della nostra app e copiarla/incollarla qui:
    1. Vai ad Android Studio e apri il terminale.
    2. Esegui questo comando:
      keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
      
      Questo comando è progettato per elencare i dettagli di una voce specifica (alias) all'interno di un keystore.
      • -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.
    3. Copia il valore della firma SHA-1:
    Firma SHA
    1. Torna alla finestra di Google Cloud e incolla il valore della firma SHA-1:
  6. La schermata dovrebbe ora essere simile a questa e puoi fare clic su Crea:Dettagli client AndroidClient Android

Crea un client web OAuth 2.0

  1. 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.
  2. Assegna un nome al client (questo sarà il client OAuth): Dettagli del client web
  3. Fai clic su CreaClient web.
  4. Copia l'ID client dalla finestra popup, ti servirà in seguitoCopia ID client

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

  1. In Android Studio, apri Device ManagerGestione dispositivi.
  2. Fai clic sul pulsante + > Crea dispositivo virtualeCrea dispositivo virtuale
  3. 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 AvantiMedium Phone
  4. 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 FineConfigura il dispositivo virtuale
  5. Dovresti vedere il nuovo dispositivo in Device Manager. Per verificare che il dispositivo funzioni, fai clic su Esegui dispositivo accanto al dispositivo che hai appena creatoEsegui dispositivo 2.
  6. Il dispositivo dovrebbe essere in esecuzione.Dispositivo di corsa

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.

  1. Vai a Impostazioni:
    1. Fai clic al centro dello schermo sul dispositivo virtuale e scorri verso l'alto.
    Fai clic e scorri
    1. Cerca l'app Impostazioni e fai clic
    App Impostazioni
  2. Fai clic su Google in ImpostazioniServizi e preferenze Google.
  3. Fai clic su Accedi e segui le istruzioni per accedere al tuo Account GoogleAccesso al dispositivo.
  1. Ora dovresti aver eseguito l'accesso sul dispositivoAccesso eseguito 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
  1. Vai a File > Project Structure (Struttura progetto):Struttura del progetto
  2. Poi vai a Dipendenze > app > "+" > Dipendenza libreriaDipendenze
  3. Ora dobbiamo aggiungere le nostre librerie:
    1. Nella finestra di dialogo di ricerca, digita googleid e fai clic su Cerca.
    2. Dovrebbe esserci una sola voce. Selezionala e scegli la versione più recente disponibile (al momento di questo Codelab è la 1.1.1).
    3. Fai clic su Ok.Pacchetto di documenti di identità Google
    4. 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 artefattoAutenticazione di Play Services
  4. Fai clic su Ok.Dipendenze completate

6. Flusso del riquadro inferiore

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

  1. Per iniziare, rimuovi le funzioni Greeting() e GreetingPreview() da MainActivity.kt, non ci serviranno.
  2. 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
    
  3. 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 oggetto GetGoogleIdOption. 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'oggetto GetGoogleIdOption con la configurazione specificata.
    • val request: GetCredentialRequest = ...: crea un oggetto GetCredentialRequest. 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'oggetto GetCredentialRequest.
    • 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 precedente signIn non è riuscito, questa parte crea un nuovo GetGoogleIdOption.
      • .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 nuovo GetCredentialRequest con googleIdOptionFalse.
      • 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 oggetto GetCredentialRequest, 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 variabile result.
    • 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 tipo GetCredentialException. 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): gestisce NoCredentialException, 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.
    • catch (e: GetCredentialCustomException): gestisce le eccezioni personalizzate che potrebbero essere generate dal fornitore di credenziali.
    • catch (e: GetCredentialCancellationException): gestisce GetCredentialCancellationException, 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:

  1. Premi il pulsante Esegui:Esegui progetto
  2. Una volta eseguita l'app sull'emulatore, dovrebbe essere visualizzato il BottomSheet di accesso. Fai clic su Continua per testare l'accessoRiquadro inferiore
  3. Dovrebbe essere visualizzato un messaggio di notifica che indica che l'accesso è stato eseguito correttamente.Riquadro inferiore riuscito

7. Flusso dei pulsanti

GIF del 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

  1. Scarica il file ZIP delle icone del brand preapprovate qui.
  2. 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.
  3. Copia il file
  4. 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).Disegnabile
  5. 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 OkPulsante Aggiungi.

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 oggetto GetSignInWithGoogleOption. Questo oggetto viene utilizzato per specificare i parametri per la procedura "Accedi con Google", richiede webClientId e un nonce (una stringa casuale utilizzata per la sicurezza).
  • val request: GetCredentialRequest = GetCredentialRequest.Builder().addCredentialOption(signInWithGoogleOption).build(): viene creato un oggetto GetCredentialRequest. Questa richiesta verrà utilizzata per ottenere le credenziali utente utilizzando Gestione credenziali. GetCredentialRequest aggiunge GetSignInWithGoogleOption creato in precedenza come opzione per richiedere una credenziale "Accedi con Google".
  • coroutineScope.launch { ... }: un CoroutineScope per gestire le operazioni asincrone (utilizzando le coroutine).
    • signIn(request, context): chiama la funzione signIn() 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:

  1. Premi il pulsante Esegui:Esegui progetto
  2. Una volta eseguita l'app sull'emulatore, dovrebbe essere visualizzato BottomSheet. Fai clic all'esterno per chiuderla.Tocca qui
  3. Ora dovresti vedere il pulsante che abbiamo creato nell'app. Fai clic per visualizzare la finestra di dialogo di accessoFinestra di dialogo Accedi
  4. 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

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
}