Découvrez comment implémenter Se connecter avec Google dans votre application Android.

1. Avant de commencer

Dans cet atelier de programmation, vous apprendrez à implémenter Se connecter avec Google sur Android à l'aide de Credential Manager.

Prérequis

  • Connaissances de base de l'utilisation de Kotlin pour le développement Android
  • Connaissances de base de Jetpack Compose (pour en savoir plus, cliquez ici)

Points abordés

  • Créer un projet Google Cloud
  • Créer des clients OAuth dans la console Google Cloud
  • Implémenter Se connecter avec Google à l'aide du flux Bottom Sheet
  • Implémenter Se connecter avec Google à l'aide du flux de bouton

Ce dont vous avez besoin

2. Créer un projet Android Studio

Durée : 3:00 – 5:00

Pour commencer, nous devons créer un projet dans Android Studio :

  1. Ouvrir Android Studio
  2. Cliquez sur Nouveau projet Bienvenue dans Android Studio.
  3. Sélectionnez Téléphone et tablette et Activité vide.Projet Android Studio
  4. Cliquez sur Suivant.
  5. Il est maintenant temps de configurer quelques éléments du projet :
    • Nom : nom de votre projet
    • Nom du package : il sera renseigné automatiquement en fonction du nom de votre projet.
    • Emplacement d'enregistrement : par défaut, il s'agit du dossier dans lequel Android Studio enregistre vos projets. Vous pouvez le modifier à votre guise.
    • Minimum SDK (SDK minimal) : version la plus basse du SDK Android sur laquelle votre application est conçue pour s'exécuter. Dans cet atelier de programmation, nous utiliserons l'API 36 (Baklava).
    Projet de configuration Android Studio
  6. Cliquez sur Finish (Terminer).
  7. Android Studio crée le projet et télécharge les dépendances nécessaires pour l'application de base. Cette opération peut prendre plusieurs minutes. Pour voir ce qui se passe, cliquez simplement sur l'icône de compilation : Compiler un projet Android Studio
  8. Une fois l'opération terminée, Android Studio devrait se présenter comme suit :Projet Android Studio créé

3. Configurer votre projet Google Cloud

Créer un projet Google Cloud

  1. Accédez à la console Google Cloud.
  2. Ouvrez votre projet ou créez-en unCréer un projet GCPGCP create new project 2GCP create new project 3
  3. Cliquez sur API et servicesAPI et services GCP.
  4. Accédez à l'écran de consentement OAuth.Écran de consentement OAuth GCP
  5. Vous devrez remplir les champs de la section Présentation pour continuer. Cliquez sur Commencer pour commencer à remplir les informations suivantes :Bouton "Commencer" de GCP
    • Nom de l'application : nom de cette application, qui doit être identique à celui que vous avez utilisé lors de la création du projet dans Android Studio
    • Adresse e-mail d'assistance utilisateur : le compte Google avec lequel vous êtes connecté et les groupes Google que vous gérez s'affichent.
    Infos sur l'application GCP
    • Audience :
      • "Interne" pour une application utilisée uniquement au sein de votre organisation. Si aucun projet Google Cloud n'est associé à une organisation, vous ne pourrez pas le sélectionner.
      • Nous allons utiliser "Externe".
    Audience GCP
    • Coordonnées : vous pouvez indiquer l'adresse e-mail de votre choix pour être le point de contact de l'application.
    Coordonnées GCP
    • Consultez le Règlement sur les données utilisateur dans les services d'API Google.
  6. Une fois que vous avez lu et accepté le Règlement sur les données utilisateur, cliquez sur Créer.Créer un compte GCP

Configurer des clients OAuth

Maintenant que nous avons configuré un projet Google Cloud, nous devons ajouter un client Web et un client Android afin de pouvoir effectuer des appels d'API au serveur backend OAuth avec leurs ID client.

Pour le client Web Android, vous aurez besoin de ce qui suit :

  • Nom du package de votre application (par exemple, com.example.example)
  • Signature SHA-1 de votre application
    • Qu'est-ce qu'une signature SHA-1 ?
      • L'empreinte SHA-1 est un hachage cryptographique généré à partir de la clé de signature de votre application. Il sert d'identifiant unique pour le certificat de signature de votre application. Considérez-le comme une "signature" numérique pour votre application.
    • Pourquoi avons-nous besoin de la signature SHA-1 ?
      • L'empreinte SHA-1 garantit que seule votre application, signée avec votre clé de signature spécifique, peut demander des jetons d'accès à l'aide de votre ID client OAuth 2.0. Cela empêche d'autres applications (même celles portant le même nom de package) d'accéder aux ressources et aux données utilisateur de votre projet.
      • Envisagez les choses de cette façon :
        • La clé de signature de votre application est comme la clé physique de la "porte" de votre application. Il permet d'accéder au fonctionnement interne de l'application.
        • L'empreinte SHA-1 est comme un badge d'accès unique lié à votre clé physique. Il s'agit d'un code spécifique qui identifie cette clé.
        • L'ID client OAuth 2.0 est comme un code d'accès à une ressource ou un service Google spécifique (par exemple, Google Sign-in).
        • Lorsque vous fournissez l'empreinte SHA-1 lors de la configuration du client OAuth, vous indiquez à Google : "Seule la carte d'accès avec cet ID spécifique (SHA-1) peut ouvrir ce code d'accès (ID client)." Cela permet de s'assurer que seule votre application peut accéder aux services Google associés à ce code d'accès."

Pour le client Web, nous n'avons besoin que du nom que vous souhaitez utiliser pour identifier le client dans la console.

Créer un client Android OAuth 2.0

  1. Accédez à la page Clients.Clients GCP
  2. Cliquez sur Créer un clientCréer des clients GCP.
  3. Sélectionnez Android comme type d'application.
  4. Vous devrez spécifier le nom du package de votre application.
  5. Dans Android Studio, nous devons obtenir la signature SHA-1 de notre application et la copier/coller ici :
    1. Accédez à Android Studio et ouvrez le terminal.
    2. Exécutez cette commande :
      keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
      
      Cette commande est conçue pour lister les détails d'une entrée (alias) spécifique dans un keystore.
      • -list : cette option indique à keytool de lister le contenu du keystore.
      • -v : cette option active la sortie détaillée, qui fournit des informations plus détaillées sur l'entrée.
      • -keystore ~/.android/debug.keystore : spécifie le chemin d'accès au fichier keystore.
      • -alias androiddebugkey : alias (nom d'entrée) de la clé que vous souhaitez inspecter.
      • -storepass android : mot de passe du fichier de clés.
      • -keypass android : fournit le mot de passe de la clé privée de l'alias spécifié.
    3. Copiez la valeur de la signature SHA-1 :
    Signature SHA
    1. Revenez à la fenêtre Google Cloud et collez la valeur de la signature SHA-1 :
  6. Votre écran devrait maintenant ressembler à ceci. Vous pouvez cliquer sur Créer :Informations sur le client AndroidClient Android

Créer un client Web OAuth 2.0

  1. Pour créer un ID client d'application Web, répétez les étapes 1 et 2 de la section Créer un client Android, puis sélectionnez Application Web pour le type d'application.
  2. Donnez un nom au client (il s'agira du client OAuth) : Détails du client Web
  3. Cliquez sur CréerClient Web.
  4. Copiez l'ID client depuis la fenêtre pop-up. Vous en aurez besoin plus tard.Copier l'ID client

Maintenant que nos clients OAuth sont configurés, nous pouvons revenir à Android Studio pour créer notre application Android Se connecter avec Google.

4. Configurer un appareil virtuel Android

Pour tester rapidement votre application sans appareil Android physique, vous devez créer un appareil virtuel Android sur lequel vous pourrez compiler et exécuter immédiatement votre application à partir d'Android Studio. Si vous souhaitez effectuer des tests avec un appareil Android physique, vous pouvez suivre les instructions de la documentation Android pour les développeurs.

Créer un appareil virtuel Android

  1. Dans Android Studio, ouvrez le Gestionnaire d'appareilsGestionnaire d'appareils.
  2. Cliquez sur le bouton + > Créer un appareil virtuel.Créer un appareil virtuel
  3. Vous pouvez ajouter ici tous les appareils dont vous avez besoin pour votre projet. Pour cet atelier de programmation, sélectionnez Medium Phone (Téléphone moyen), puis cliquez sur Next (Suivant).Téléphone de taille moyenne
  4. Vous pouvez maintenant configurer l'appareil pour votre projet en lui attribuant un nom unique, en choisissant la version d'Android qu'il exécutera, etc. Assurez-vous que l'API est définie sur API 36 "Baklava" ; Android 16, puis cliquez sur Terminer.Configurer un appareil virtuel
  5. Le nouvel appareil devrait apparaître dans le gestionnaire d'appareils. Pour vérifier que l'appareil fonctionne, cliquez sur Exécuter l'appareil à côté de l'appareil que vous venez de créerExécuter l'appareil 2.
  6. L'appareil devrait maintenant fonctionner.Appareil en cours d'exécution

Se connecter à l'appareil virtuel Android

L'appareil que vous venez de créer fonctionne. Pour éviter les erreurs lors du test de la connexion avec Google, nous devons maintenant nous connecter à l'appareil avec un compte Google.

  1. Accédez aux paramètres :
    1. Cliquez au centre de l'écran de l'appareil virtuel et balayez vers le haut.
    Cliquer et balayer
    1. Recherchez l'application Paramètres et cliquez dessus.
    Application Paramètres
  2. Cliquez sur Google dans ParamètresServices et préférences Google.
  3. Cliquez sur Se connecter et suivez les instructions pour vous connecter à votre compte Google.Connexion à l'appareil
  1. Vous devriez maintenant être connecté sur l'appareilAppareil connecté

Votre appareil Android virtuel est maintenant prêt à être testé.

5. Ajouter des dépendances

Durée : 5:00

Pour effectuer des appels d'API OAuth, nous devons d'abord intégrer les bibliothèques nécessaires qui nous permettent d'effectuer des demandes d'authentification et d'utiliser des ID Google pour effectuer ces demandes :

  • libs.googleid
  • libs.play.services.auth
  1. Accédez à Fichier > Structure du projet :Structure du projet
  2. Accédez ensuite à Dependencies > app > '+' > Library DependencyDépendances.
  3. Nous devons maintenant ajouter nos bibliothèques :
    1. Dans la boîte de dialogue de recherche, saisissez googleid, puis cliquez sur Rechercher.
    2. Il ne doit y avoir qu'une seule entrée. Sélectionnez-la, ainsi que la version la plus récente disponible (au moment de cet atelier de programmation, il s'agit de la version 1.1.1).
    3. Cliquez sur OK.Package d'identité Google
    4. Répétez les étapes 1 à 3, mais recherchez play-services-auth et sélectionnez la ligne avec "com.google.android.gms" comme ID de groupe et "play-services-auth" comme Nom de l'artefact.Authentification des services Play
  4. Cliquez sur OK.Dépendances terminées

6. Flux de la bottom sheet

Flux de la bottom sheet

Le flux de bottom sheet utilise l'API Credential Manager pour permettre aux utilisateurs de se connecter à votre application à l'aide de leur compte Google sur Android de manière simplifiée. Elle est conçue pour être rapide et pratique, en particulier pour les utilisateurs connus. Ce flux doit être déclenché au lancement de l'application.

Créer la demande de connexion

  1. Pour commencer, supprimez les fonctions Greeting() et GreetingPreview() de MainActivity.kt, car nous n'en aurons pas besoin.
  2. Nous devons maintenant nous assurer que les packages dont nous avons besoin sont importés pour ce projet. Ajoutez les instructions import suivantes après celles existantes, en commençant à la ligne 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. Ensuite, nous devons créer notre fonction pour élaborer la requête Bottom Sheet. Collez ce code sous 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)
   }

Voici ce que fait ce code :

fun BottomSheet(webClientId: String) {...} : crée une fonction appelée BottomSheet qui accepte un argument de chaîne appelé webClientid.

  • val context = LocalContext.current : récupère le contexte Android actuel. Cela est nécessaire pour diverses opérations, y compris le lancement de composants d'UI.
  • LaunchedEffect(Unit) { ... } : LaunchedEffect est un composable Jetpack Compose qui vous permet d'exécuter une fonction de suspension (une fonction qui peut mettre en pause et reprendre l'exécution) au cours du cycle de vie du composable. L'utilisation de Unit comme clé signifie que cet effet ne s'exécutera qu'une seule fois lors du premier lancement du composable.
    • val googleIdOption: GetGoogleIdOption = ... : crée un objet GetGoogleIdOption. Cet objet configure le type d'identifiant demandé à Google.
      • .Builder() : un modèle de création est utilisé pour configurer les options.
      • .setFilterByAuthorizedAccounts(true) : indique s'il faut autoriser l'utilisateur à sélectionner un compte Google parmi tous ceux disponibles ou uniquement parmi ceux qui ont déjà autorisé l'application. Dans ce cas, la valeur est définie sur "true", ce qui signifie que la requête sera effectuée à l'aide des identifiants que l'utilisateur a déjà autorisés pour cette application, le cas échéant.
      • .setServerClientId(webClientId) : définit l'ID client du serveur, qui est un identifiant unique pour le backend de votre application. Cette étape est obligatoire pour obtenir un jeton d'ID.
      • .setNonce(generateSecureRandomNonce()) : définit un nonce (valeur aléatoire) pour empêcher les attaques par rejeu et s'assurer que le jeton d'identité est associé à la requête spécifique.
      • .build() : crée l'objet GetGoogleIdOption avec la configuration spécifiée.
    • val request: GetCredentialRequest = ... : crée un objet GetCredentialRequest. Cet objet encapsule l'intégralité de la demande d'identifiants.
      • .Builder() : démarre le modèle de compilateur pour configurer la requête.
      • .addCredentialOption(googleIdOption) : ajoute l'option googleIdOption à la requête, en précisant que nous souhaitons demander un jeton d'ID Google.
      • .build() : crée l'objet GetCredentialRequest.
    • val e = signIn(request, context) : tente de connecter l'utilisateur avec la requête créée et le contexte actuel. Le résultat de la fonction signIn est stocké dans e. Cette variable contiendra le résultat réussi ou une exception.
    • if (e is NoCredentialException) { ... } : il s'agit d'une vérification conditionnelle. Si la fonction signIn échoue avec une exception NoCredentialException, cela signifie qu'aucun compte précédemment autorisé n'est disponible.
      • val googleIdOptionFalse: GetGoogleIdOption = ... : si le signIn précédent a échoué, cette partie crée un GetGoogleIdOption.
      • .setFilterByAuthorizedAccounts(false) : il s'agit de la différence essentielle par rapport à la première option. Elle désactive le filtrage des comptes autorisés, ce qui signifie que n'importe quel compte Google sur l'appareil peut être utilisé pour se connecter.
      • val requestFalse: GetCredentialRequest = ... : un GetCredentialRequest est créé avec le googleIdOptionFalse.
      • signIn(requestFalse, context) : cette méthode tente de connecter l'utilisateur avec la nouvelle requête qui permet d'utiliser n'importe quel compte.

En substance, ce code prépare une requête à l'API Credential Manager pour récupérer un jeton Google ID pour l'utilisateur, à l'aide des configurations fournies. La GetCredentialRequest peut ensuite être utilisée pour lancer l'UI du gestionnaire d'informations d'identification, où l'utilisateur peut sélectionner son compte Google et accorder les autorisations nécessaires.

fun generateSecureRandomNonce(byteLength: Int = 32): String : définit une fonction nommée generateSecureRandomNonce. Il accepte un argument entier byteLength (avec une valeur par défaut de 32) qui spécifie la longueur souhaitée du nonce en octets. Elle renvoie une chaîne, qui correspond à la représentation en base64 des octets aléatoires.

  • val randomBytes = ByteArray(byteLength) : crée un tableau d'octets de la longueur spécifiée (byteLength) pour contenir les octets aléatoires.
  • SecureRandom.getInstanceStrong().nextBytes(randomBytes) :
    • SecureRandom.getInstanceStrong() : permet d'obtenir un générateur de nombres aléatoires cryptographiquement sécurisé. Cela est essentiel pour la sécurité, car cela garantit que les nombres générés sont véritablement aléatoires et non prévisibles. Il utilise la source d'entropie la plus forte disponible sur le système.
    • .nextBytes(randomBytes) : cette ligne remplit le tableau randomBytes avec des octets aléatoires générés par l'instance SecureRandom.
  • return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes) :
    • Base64.getUrlEncoder() : obtient un encodeur Base64 qui utilise un alphabet sécurisé pour les URL (avec "-" et "_" au lieu de "+" et "/"). Cela permet de s'assurer que la chaîne résultante peut être utilisée en toute sécurité dans les URL sans nécessiter d'encodage supplémentaire.
    • .withoutPadding() : supprime tous les caractères de remplissage de la chaîne encodée en base64. C'est souvent souhaitable pour rendre le nonce légèrement plus court et plus compact.
    • .encodeToString(randomBytes) : cette ligne encode les octets aléatoires en chaîne Base64 et la renvoie.

En résumé, cette fonction génère un nonce aléatoire cryptographiquement sécurisé d'une longueur spécifiée, l'encode à l'aide de Base64 sécurisé pour les URL et renvoie la chaîne résultante. Il s'agit d'une pratique standard pour générer des nonces pouvant être utilisées dans des contextes sensibles en termes de sécurité.

Envoyer la demande de connexion

Maintenant que nous pouvons créer notre demande de connexion, nous pouvons utiliser Credential Manager pour nous connecter. Pour ce faire, nous devons créer une fonction qui gère la transmission des demandes de connexion à l'aide du Gestionnaire d'identifiants, tout en gérant les exceptions courantes que nous pourrions rencontrer.

Pour ce faire, vous pouvez coller cette fonction sous la fonction 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
}

Voici ce que fait le code :

suspend fun signIn(request: GetCredentialRequest, context: Context): Exception? : définit une fonction de suspension nommée signIn. Cela signifie qu'il peut être mis en pause et repris sans bloquer le thread principal.Il renvoie un Exception? qui sera nul si la connexion réussit ou l'exception spécifique si la connexion échoue.

Elle comporte deux paramètres :

  • request : objet GetCredentialRequest contenant la configuration du type d'identifiant à récupérer (par exemple, ID Google).
  • context : contexte Android nécessaire pour interagir avec le système.

Pour le corps de la fonction :

  • val credentialManager = CredentialManager.create(context) : crée une instance de CredentialManager, qui est l'interface principale pour interagir avec l'API Credential Manager. C'est ainsi que l'application lancera le flux de connexion.
  • val failureMessage = "Sign in failed!" : définit une chaîne (failureMessage) à afficher dans un toast en cas d'échec de la connexion.
  • var e: Exception? = null : cette ligne initialise une variable e pour stocker toute exception pouvant survenir au cours du processus, en commençant par null.
  • delay(250) : introduit un délai de 250 millisecondes. Il s'agit d'une solution de contournement pour un problème potentiel où NoCredentialException peut être généré immédiatement au démarrage de l'application, en particulier lors de l'utilisation d'un flux BottomSheet. Cela laisse le temps au système d'initialiser le gestionnaire d'identifiants.
  • try { ... } catch (e: Exception) { ... } : un bloc try-catch est utilisé pour un traitement robuste des erreurs. Cela garantit que si une erreur se produit lors du processus de connexion, l'application ne plante pas et peut gérer l'exception de manière fluide.
    • val result = credentialManager.getCredential(request = request, context = context) : c'est ici que l'appel à l'API Credential Manager a lieu et lance le processus de récupération des identifiants. Il prend la requête et le contexte comme entrée, puis présente une UI à l'utilisateur pour qu'il sélectionne un identifiant. Si l'opération réussit, elle renvoie un résultat contenant l'identifiant sélectionné. Le résultat de cette opération, GetCredentialResponse, est stocké dans la variable result.
    • Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show() : affiche un court message toast indiquant que la connexion a réussi.
    • Log.i(TAG, "Sign in Successful!") : enregistre un message amusant et réussi dans Logcat.
    • catch (e: GetCredentialException) : gère les exceptions de type GetCredentialException. Il s'agit d'une classe parente pour plusieurs exceptions spécifiques qui peuvent se produire lors du processus de récupération des identifiants.
    • catch (e: GoogleIdTokenParsingException) : gère les exceptions qui se produisent en cas d'erreur lors de l'analyse du jeton d'ID Google.
    • catch (e: NoCredentialException) : gère NoCredentialException, qui est déclenché lorsqu'aucune identifiant n'est disponible pour l'utilisateur (par exemple, s'il n'en a enregistré aucun ou s'il ne possède pas de compte Google).
      • Cette fonction renvoie l'exception stockée dans e, NoCredentialException, ce qui permet à l'appelant de gérer le cas spécifique si aucun identifiant n'est disponible.
    • catch (e: GetCredentialCustomException) : gère les exceptions personnalisées qui peuvent être générées par le fournisseur d'identifiants.
    • catch (e: GetCredentialCancellationException) : gère GetCredentialCancellationException, qui est déclenché lorsque l'utilisateur annule le processus de connexion.
    • Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show() : affiche un message toast indiquant que la connexion a échoué à l'aide de failureMessage.
    • Log.e(TAG, "", e) : enregistre l'exception dans le logcat Android à l'aide de Log.e, qui est utilisé pour les erreurs. Cela inclura la trace de la pile de l'exception pour faciliter le débogage. Il inclut également l'émoticône de colère pour le fun.
  • return e : la fonction renvoie l'exception si une exception a été détectée, ou null si la connexion a réussi.

En résumé, ce code permet de gérer la connexion des utilisateurs à l'aide de l'API Credential Manager, gère l'opération asynchrone, gère les erreurs potentielles et fournit des commentaires à l'utilisateur via des toasts et des journaux, tout en ajoutant une touche d'humour à la gestion des erreurs.

Implémenter le flux de la bottom sheet dans l'application

Nous pouvons maintenant configurer un appel pour déclencher le flux BottomSheet dans notre classe MainActivity en utilisant le code suivant et notre ID client d'application Web que nous avons copié précédemment depuis la console Google Cloud :

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)
                }
            }
        }
    }
}

Nous pouvons maintenant enregistrer notre projet (Fichier > Enregistrer) et l'exécuter :

  1. Appuyez sur le bouton d'exécution :Exécuter le projet
  2. Une fois votre application exécutée sur l'émulateur, la BottomSheet de connexion devrait s'afficher. Cliquez sur Continuer pour tester la connexion.Bottom sheet
  3. Un message Toast devrait s'afficher pour vous indiquer que la connexion a réussi.Bottom sheet de réussite

7. Flux de boutons

GIF du flux de boutons

Le flux de bouton Se connecter avec Google permet aux utilisateurs de s'inscrire ou de se connecter plus facilement à votre application Android à l'aide de leur compte Google existant. Ils y seront redirigés s'ils ferment la feuille inférieure ou s'ils préfèrent utiliser explicitement leur compte Google pour se connecter ou s'inscrire. Pour les développeurs, cela signifie une intégration plus fluide et moins de difficultés lors de l'inscription.

Bien que cela puisse être fait avec un bouton Jetpack Compose prêt à l'emploi, nous utiliserons une icône de marque pré-approuvée de la page Consignes relatives à l'identité visuelle de Se connecter avec Google.

Ajouter une icône de marque au projet

  1. Téléchargez le fichier ZIP des icônes de marques préapprouvées.
  2. Décompressez le fichier signin-assest.zip depuis vos téléchargements (cela varie en fonction du système d'exploitation de votre ordinateur). Vous pouvez maintenant ouvrir le dossier signin-assets et parcourir les icônes disponibles. Pour cet atelier de programmation, nous utiliserons signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png.
  3. Copier le fichier
  4. Collez-le dans le projet Android Studio sous res > drawable en effectuant un clic droit sur le dossier drawable, puis en cliquant sur Coller (vous devrez peut-être développer le dossier res pour le voir).Drawable
  5. Une boîte de dialogue s'affiche pour vous inviter à renommer le fichier et à confirmer le répertoire dans lequel il sera ajouté. Renommez l'élément siwg_button.png, puis cliquez sur OK.Ajouter un bouton

Code du flux de bouton

Ce code utilisera la même fonction signIn() que pour BottomSheet(), mais utilisera GetSignInWithGoogleOption au lieu de GetGoogleIdOption, car ce flux n'exploite pas les identifiants et les clés d'accès stockés sur l'appareil pour afficher les options de connexion. Voici le code que vous pouvez coller sous la fonction 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)
    )
}

Voici ce que fait le code :

fun ButtonUI(webClientId: String) : déclare une fonction nommée ButtonUI qui accepte un webClientId (ID client de votre projet Google Cloud) comme argument.

val context = LocalContext.current : récupère le contexte Android actuel. Cela est nécessaire pour diverses opérations, y compris le lancement de composants d'UI.

val coroutineScope = rememberCoroutineScope() : crée un champ d'application de coroutine. Il permet de gérer les tâches asynchrones, ce qui permet au code de s'exécuter sans bloquer le thread principal. rememberCoroutineScope() est une fonction composable de Jetpack Compose qui fournit un champ d'application lié au cycle de vie du composable.

val onClick: () -> Unit = { ... } : crée une fonction lambda qui sera exécutée lorsque l'utilisateur cliquera sur le bouton. La fonction lambda :

  • val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(serverClientId = webClientId).setNonce(generateSecureRandomNonce()).build() : cette partie crée un objet GetSignInWithGoogleOption. Cet objet est utilisé pour spécifier les paramètres du processus "Se connecter avec Google". Il nécessite webClientId et un nonce (chaîne aléatoire utilisée pour la sécurité).
  • val request: GetCredentialRequest = GetCredentialRequest.Builder().addCredentialOption(signInWithGoogleOption).build() : crée un objet GetCredentialRequest. Cette requête sera utilisée pour obtenir les identifiants de l'utilisateur à l'aide du Gestionnaire d'identifiants. GetCredentialRequest ajoute l'GetSignInWithGoogleOption créé précédemment en tant qu'option, afin de demander un identifiant "Se connecter avec Google".
  • coroutineScope.launch { ... } : CoroutineScope pour gérer les opérations asynchrones (à l'aide de coroutines).
    • signIn(request, context) : appelle notre fonction signIn() définie précédemment.

Image(...) : cela affiche une image à l'aide de painterResource qui charge l'image R.drawable.siwg_button.

  • Modifier.fillMaxSize().clickable(enabled = true, onClick = onClick) :
    • fillMaxSize() : permet à l'image de remplir l'espace disponible.
    • clickable(enabled = true, onClick = onClick) : rend l'image cliquable et exécute la fonction lambda onClick précédemment définie lorsque l'utilisateur clique dessus.

En résumé, ce code configure un bouton "Se connecter avec Google" dans une UI Jetpack Compose. Lorsque l'utilisateur clique sur le bouton, une demande d'informations d'identification est préparée pour lancer Credential Manager et permettre à l'utilisateur de se connecter avec son compte Google.

Nous devons maintenant mettre à jour la classe MainActivity pour exécuter notre fonction 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)
                    }
                }
            }
        }
    }
}

Nous pouvons maintenant enregistrer notre projet (Fichier > Enregistrer) et l'exécuter :

  1. Appuyez sur le bouton d'exécution :Exécuter le projet
  2. Une fois l'application exécutée sur l'émulateur, la feuille BottomSheet devrait s'afficher. Cliquez en dehors pour la fermer.Appuyez ici
  3. Le bouton que nous avons créé devrait maintenant s'afficher dans l'application. Cliquez dessus pour afficher la boîte de dialogue de connexion.Boîte de dialogue de connexion
  4. Cliquez sur votre compte pour vous connecter.

8. Conclusion

Vous avez terminé cet atelier de programmation. Pour en savoir plus ou obtenir de l'aide concernant Se connecter avec Google sur Android, consultez la section "Questions fréquentes" ci-dessous :

Questions fréquentes

Code complet de MainActivity.kt

Voici le code complet de MainActivity.kt à titre de référence :

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
}