1. Prima di iniziare
Scopri come implementare Accedi con Google su Android utilizzando Gestore delle credenziali.
Prerequisiti
- Conoscenza di base di Kotlin per lo sviluppo di Android.
- Conoscenza di base di Jetpack Compose (scopri di più).
Obiettivi didattici
- Crea un progetto Google Cloud e client OAuth.
- Implementa il flusso di accesso del riquadro inferiore.
- Implementa il flusso di accesso del pulsante esplicito.
Cosa serve
- Android Studio installato.
- Un computer che soddisfi i requisiti di sistema di Android Studio e dell'emulatore.
- Java Development Kit (JDK) installato.
2. Crea un progetto Android Studio
Per iniziare, crea un nuovo progetto in Android Studio:
- Apri Android Studio e fai clic su New Project (Nuovo progetto).

- Seleziona Phone and Tablet > Empty Activity (Smartphone e tablet > Attività vuota), quindi fai clic su Next (Avanti).

- Configura le impostazioni del progetto:
- Name (Nome): scegli un nome per il progetto.
- Nome pacchetto: utilizza il nome predefinito o scegli il tuo.
- Minimum SDK (SDK minimo): seleziona la versione stabile più recente o > Android 14.

- Fai clic su Finish (Fine) e attendi il completamento della build iniziale del progetto.

3. Configura il progetto Google Cloud
Crea un progetto Google Cloud
- Vai alla console Google Cloud e seleziona o crea un progetto.

- Vai ad API e servizi > Schermata per il consenso OAuth.

- Fai clic su Inizia e compila i campi obbligatori:
- Nome app: utilizza il nome della tua app per Android.
- Email di assistenza utenti: seleziona il tuo Account Google.
- Pubblico: seleziona Esterno.
- Informazioni di contatto: inserisci il tuo indirizzo email.

- Esamina le Norme relative ai dati utente: servizi API di Google e fai clic su Crea.

Configura i client OAuth
Per ottenere gli ID client per l'autenticazione, devi creare un client web e un client Android nella console Google Cloud.
- Client Android: protegge le richieste verificando il nome del pacchetto e la firma SHA-1 dell'app.
- Client web: funge da client di backend per il servizio Accedi con Google.
Crea un client OAuth 2.0 per Android
- Nella pagina Client, fai clic su Crea client e seleziona Android come Tipo di applicazione.

- Inserisci il nome del pacchetto dell'app (corrispondente alla riga 1 di
MainActivity.kt). - Genera la firma SHA-1. Apri il terminale di Android Studio ed esegui:macOS/Linux:
Windows:keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
keytool -list -v -keystore "C:\Users\USERNAME\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
- Copia l'impronta SHA-1 dall'output comando, incollala nel campo Impronta SHA-1 nella console e fai clic su Crea.

Crea un client OAuth 2.0 per il web
- Fai di nuovo clic su Crea client e seleziona Applicazione web come Tipo di applicazione.
- Assegna un nome al client web, lascia vuoti i campi URL/Origini e fai clic su Crea.

- Copia l'ID client generato dalla finestra di dialogo di conferma. Lo utilizzerai nel codice Kotlin.

4. Configura un dispositivo virtuale Android
Per testare l'app, puoi utilizzare un dispositivo Android fisico o un Android Virtual Device (AVD).
Crea ed esegui l'AVD
- In Android Studio, apri Device Manager (Gestione dispositivi), fai clic su Create Virtual Device (Crea dispositivo virtuale) (o sull'icona + ) e seleziona Medium Phone (Smartphone di medie dimensioni).
- Seleziona la versione stabile più recente come immagine di sistema e fai clic su Fine.
- Avvia l'emulatore facendo clic sull'icona Riproduci/Esegui accanto al dispositivo.

Accedi a un Account Google sul dispositivo
- Nell'emulatore, apri l'app Impostazioni e vai a Google.
- Fai clic su Accedi al tuo Account Google e segui le istruzioni.

5. Aggiungi dipendenze
Aggiungi al progetto le librerie necessarie per l'autenticazione e l'integrazione dell'ID Google:
- Vai a File > Struttura progetto > Dipendenze > app.
- Fai clic su + > Dipendenza libreria , cerca
com.google.android.libraries.identity.googleid:googleide seleziona l'ultima versione (ad es.1.1.1). - Fai di nuovo clic su + > Dipendenza libreria , cerca
play-services-authe seleziona la libreria con l'ID gruppocom.google.android.gms. - Fai clic su Ok per applicare le modifiche e sincronizzare il progetto.

6. Implementa il flusso del riquadro inferiore

Il flusso del riquadro inferiore utilizza l'API Credential Manager per consentire agli utenti di accedere alla tua app utilizzando i propri Account Google su Android in modo semplificato. Progettato per la velocità e la praticità, soprattutto per gli utenti che ritornano, questo flusso deve essere attivato all'avvio dell'app.
Crea la richiesta di accesso
- Per iniziare, apri
MainActivity.kte rimuovi le funzioni predefiniteGreeting()eGreetingPreview(). - Aggiungi le seguenti istruzioni di importazione dopo quelle esistenti a partire dalla riga 3:
import android.content.Context import android.os.Build import android.util.Log import android.widget.Toast import androidx.annotation.RequiresApi import androidx.compose.foundation.Image import androidx.compose.foundation.clickable 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.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.credentials.CredentialManager import androidx.credentials.CustomCredential import androidx.credentials.GetCredentialRequest import androidx.credentials.exceptions.GetCredentialCancellationException import androidx.credentials.exceptions.GetCredentialCustomException import androidx.credentials.exceptions.GetCredentialException import androidx.credentials.exceptions.NoCredentialException import com.google.android.libraries.identity.googleid.GetGoogleIdOption import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException import java.security.SecureRandom import java.util.Base64 import kotlinx.coroutines.delay import kotlinx.coroutines.launch const val TAG = "MainActivity" - Aggiungi questa funzione Composable sotto la classe
MainActivitynel fileMainActivity.kt:@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) }
Analisi del codice
LaunchedEffect(Unit): attiva il flusso di accesso immediatamente alla prima visualizzazione del Composable.GetGoogleIdOption.Builder(): configura la richiesta del token ID Google.setFilterByAuthorizedAccounts(true): tenta prima un accesso silenzioso filtrando gli account che l'utente ha già autorizzato per questa app, riducendo al minimo l'attrito per gli utenti che ritornano.setNonce(...): passa un nonce casuale sicuro generato per ogni richiesta dagenerateSecureRandomNonce()per impedire attacchi di riproduzione.
signIn(request, context): esegue la richiesta. Se la richiesta non va a buon fine con un'eccezioneNoCredentialException(ovvero non esiste un account autorizzato in precedenza), il flusso torna asetFilterByAuthorizedAccounts(false)per consentire all'utente di selezionare uno qualsiasi degli Account Google con cui ha eseguito l'accesso sul dispositivo.
Effettua la richiesta di accesso
Una volta creata la richiesta di accesso, puoi utilizzare Gestore delle credenziali per completare la procedura di accesso. Crea una funzione denominata signIn che esegue la richiesta e gestisce le eccezioni comuni che potrebbero verificarsi.
Aggiungi questa funzione sotto la funzione BottomSheet nel file MainActivity.kt:
@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!"
//using delay() here helps prevent NoCredentialException when the BottomSheet Flow is triggered
//on the initial running of our app
delay(250)
return 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())
val credential = result.credential
if (credential is CustomCredential &&
credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data)
Log.i(TAG, "Signed in as: ${googleIdTokenCredential.id}")
}
Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
Log.i(TAG, "(☞゚ヮ゚)☞ Sign in Successful! ☜(゚ヮ゚☜)")
null
} catch (e: GoogleIdTokenParsingException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with parsing received GoogleIdToken", e)
e
} catch (e: NoCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": No credentials found", e)
e
} catch (e: GetCredentialCancellationException) {
Toast.makeText(context, "Sign-in cancelled", Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Sign-in was cancelled", e)
e
} catch (e: GetCredentialCustomException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with custom credential request", e)
e
} catch (e: GetCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Failure getting credentials", e)
e
}
}
Analisi del codice
credentialManager.getCredential(...): chiama l'API Credential Manager per visualizzare il riquadro inferiore o la finestra di dialogo del selettore dell'account di sistema.delay(250): mette in pausa brevemente per evitare una race condition quando il riquadro inferiore viene attivato immediatamente all'avvio dell'app prima che il servizio Gestore delle credenziali abbia completato l'inizializzazione.- Gestione delle eccezioni: rileva e registra gli errori comuni delle credenziali (ad es. annullamento, credenziali mancanti o problemi di analisi dei token) e fornisce feedback agli utenti tramite avvisi popup.
Attiva il flusso del riquadro inferiore
Aggiorna la classe MainActivity per chiamare BottomSheet() all'avvio. Sostituisci YOUR_CLIENT_ID_HERE con l'ID client dell'applicazione web:
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)
}
}
}
}
}
Salva il progetto (File > Salva) ed esegui l'applicazione:
- Premi il pulsante Esegui:

- Quando l'app viene avviata sull'emulatore, dovrebbe essere visualizzato il riquadro inferiore di accesso. Fai clic su Continua per testare il flusso.

- Dovrebbe essere visualizzata una notifica toast che conferma l'accesso.

7. Implementa il flusso del pulsante

Il flusso del pulsante offre agli utenti un'opzione esplicita per accedere o registrarsi. L'utilizzo del branding standard garantisce un'esperienza coerente. Utilizza asset pre-approvati conformi alle Linee guida per il branding di Accedi con Google.
Aggiungi l'icona del brand
- Scarica gli asset del brand qui ed estrai il file ZIP.
- Copia
signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png. - In Android Studio, incolla il file nella cartella res > drawable , rinominalo in
siwg_button.pnge fai clic su OK.
Codice del flusso del pulsante
Questo flusso riutilizza la stessa funzione helper signIn, ma passa un GetSignInWithGoogleOption anziché GetGoogleIdOption. A differenza del flusso del riquadro inferiore, il flusso del pulsante esplicito non pre-filtra né richiede automaticamente le credenziali o le passkey memorizzate. Incolla questa funzione Composable 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)
)
}
Analisi del codice
GetSignInWithGoogleOption: a differenza del flusso del riquadro inferiore, il flusso del pulsante esplicito utilizza questa opzione per chiedere agli utenti di selezionare il proprio Account Google senza filtro automatico.coroutineScope.launch: avvia una coroutine per eseguire in modo asincrono la funzione di sospensionesignInquando si fa clic sul pulsante.Image: visualizza il drawablesiwg_buttoncon il brand e collega un listener di clic per attivare il flusso.
Aggiungi il pulsante al layout dell'interfaccia utente
Aggiorna il layout MainActivity per visualizzare sia BottomSheet automatico sia ButtonUI esplicito allineati verticalmente:
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)
}
}
}
}
}
}
Testa il flusso del pulsante
- Esegui l'applicazione.
- Chiudi il riquadro inferiore iniziale facendo clic all'esterno dell'area del riquadro.
- Fai clic sul pulsante Accedi con Google per avviare la finestra di dialogo di accesso e seleziona il tuo account.

- Verifica il risultato: controlla Logcat per confermare la stampa del nome utente/indirizzo email.
8. Conclusione
Complimenti! Hai implementato correttamente Accedi con Google utilizzando Android Gestore delle credenziali.
Risorse aggiuntive
- Documentazione di Android Credential Manager
- Guida alla risoluzione dei problemi di Gestore delle credenziali
- Domande frequenti su Gestore delle credenziali
- Assistenza di StackOverflow
Codice completo di MainActivity.kt
Di seguito è riportato 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.util.Log
import android.widget.Toast
import androidx.credentials.exceptions.GetCredentialException
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)
}
}
}
}
}
}
@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()
coroutineScope.launch {
signIn(request, context)
}
}
Image(
painter = painterResource(id = R.drawable.siwg_button),
contentDescription = "",
modifier = Modifier
.fillMaxSize()
.clickable(onClick = onClick)
)
}
fun generateSecureRandomNonce(byteLength: Int = 32): String {
val randomBytes = ByteArray(byteLength)
SecureRandom.getInstanceStrong().nextBytes(randomBytes)
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
}
@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!"
//using delay() here helps prevent NoCredentialException when the BottomSheet Flow is triggered
//on the initial running of our app
delay(250)
return 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())
val credential = result.credential
if (credential is CustomCredential &&
credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data)
Log.i(TAG, "Signed in as: ${googleIdTokenCredential.id}")
}
Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
Log.i(TAG, "(☞゚ヮ゚)☞ Sign in Successful! ☜(゚ヮ゚☜)")
null
} catch (e: GoogleIdTokenParsingException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with parsing received GoogleIdToken", e)
e
} catch (e: NoCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": No credentials found", e)
e
} catch (e: GetCredentialCancellationException) {
Toast.makeText(context, "Sign-in cancelled", Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Sign-in was cancelled", e)
e
} catch (e: GetCredentialCustomException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with custom credential request", e)
e
} catch (e: GetCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Failure getting credentials", e)
e
}
}