Informationen zum Implementieren von „Über Google anmelden“ in Ihre Android-App

1. Hinweis

In diesem Codelab erfahren Sie, wie Sie „Über Google anmelden“ auf Android mit Credential Manager implementieren.

Vorbereitung

  • Grundkenntnisse in der Verwendung von Kotlin für die Android-Entwicklung
  • Grundlegendes Verständnis von Jetpack Compose (Weitere Informationen)

Lerninhalte

  • Google Cloud-Projekt erstellen
  • OAuth-Clients in der Google Cloud Console erstellen
  • „Über Google anmelden“ mit dem Bottom Sheet-Ablauf implementieren
  • „Über Google anmelden“ mit dem Schaltflächenablauf implementieren

Voraussetzungen

2. Android Studio-Projekt erstellen

Dauer: 3:00–5:00

Zuerst müssen wir ein neues Projekt in Android Studio erstellen:

  1. Android Studio öffnen
  2. Klicken Sie auf Neues ProjektWillkommen bei Android Studio.
  3. Wählen Sie Smartphone und Tablet und Leere Aktivität aus.Android Studio-Projekt
  4. Klicken Sie auf Weiter.
  5. Als Nächstes richten Sie einige Teile des Projekts ein:
    • Name: Dies ist der Name Ihres Projekts.
    • Paketname: Wird automatisch basierend auf Ihrem Projektnamen eingefügt.
    • Speicherort: Standardmäßig sollte hier der Ordner angegeben sein, in dem Android Studio Ihre Projekte speichert. Sie können den Speicherort jederzeit ändern.
    • Min. SDK-Version: Dies ist die niedrigste Version des Android SDK, für die Ihre App entwickelt wurde. In diesem Codelab verwenden wir API 36 (Baklava).
    Android Studio-Einrichtungsprojekt
  6. Klicken Sie auf Fertigstellen.
  7. Android Studio erstellt das Projekt und lädt alle erforderlichen Abhängigkeiten für die Basisanwendung herunter. Das kann einige Minuten dauern. Klicken Sie dazu einfach auf das Build-Symbol:Android Studio-Projektentwicklung
  8. Wenn das abgeschlossen ist, sollte Android Studio so aussehen:Android Studio-Projekt erstellt

3. Google Cloud-Projekt einrichten

Google Cloud-Projekt erstellen

  1. Öffnen Sie die Google Cloud Console
  2. Projekt öffnen oder neues Projekt erstellenGCP-Projekt erstellenGCP-Projekt erstellen 2GCP-Projekt erstellen 3
  3. Klicken Sie auf APIs & DiensteGCP APIs und ‑Dienste.
  4. Öffnen Sie den OAuth-Zustimmungsbildschirm.GCP-OAuth-Zustimmungsbildschirm
  5. Sie müssen die Felder unter Übersicht ausfüllen, um fortzufahren. Klicken Sie auf Jetzt starten, um diese Informationen einzugeben:Schaltfläche „Erste Schritte mit GCP“
    • App-Name: Name dieser App. Er sollte mit dem Namen übereinstimmen, den Sie beim Erstellen des Projekts in Android Studio verwendet haben.
    • E-Mail-Adresse des Nutzersupports: Hier sehen Sie das Google-Konto, mit dem Sie sich anmelden, und alle Google-Gruppen, die Sie verwalten.
    GCP-App-Informationen
    • Zielgruppe:
      • „Intern“ für eine App, die nur innerhalb Ihrer Organisation verwendet wird. Wenn dem Google Cloud-Projekt keine Organisation zugeordnet ist, können Sie diese Option nicht auswählen.
      • Wir verwenden „Extern“.
    GCP-Zielgruppe
    • Kontaktdaten: Hier kann eine beliebige E‑Mail-Adresse angegeben werden, die als Kontakt für die Anwendung dienen soll.
    GCP-Kontaktinformationen
    • Lesen Sie die Richtlinie zu Nutzerdaten für Google API-Dienste.
  6. Nachdem Sie die Richtlinie zu Nutzerdaten gelesen und ihr zugestimmt haben, klicken Sie auf ErstellenGCP Create

OAuth-Clients einrichten

Nachdem wir ein Google Cloud-Projekt eingerichtet haben, müssen wir einen Webclient und einen Android-Client hinzufügen, damit wir mit ihren Client-IDs API-Aufrufe an den OAuth-Backend-Server senden können.

Für den Android-Webclient benötigen Sie Folgendes:

  • Paketname Ihrer App (z. B. com.example.example)
  • SHA-1-Signatur Ihrer App
    • Was ist eine SHA‑1-Signatur?
      • Der SHA-1-Fingerabdruck ist ein kryptografischer Hash, der aus dem Signaturschlüssel Ihrer App generiert wird. Sie dient als eindeutige Kennung für das Signaturzertifikat Ihrer App. Sie können sich das wie eine digitale „Signatur“ für Ihre App vorstellen.
    • Warum benötigen wir die SHA1-Signatur?
      • Der SHA‑1-Fingerabdruck sorgt dafür, dass nur Ihre App, die mit Ihrem spezifischen Signaturschlüssel signiert ist, Zugriffstokens mit Ihrer OAuth 2.0-Client-ID anfordern kann. So wird verhindert, dass andere Apps (auch solche mit demselben Paketnamen) auf die Ressourcen und Nutzerdaten Ihres Projekts zugreifen können.
      • Stellen Sie es sich so vor:
        • Der Signaturschlüssel Ihrer App ist wie der physische Schlüssel zur „Tür“ Ihrer App. Sie ermöglicht den Zugriff auf die Funktionsweise der App.
        • Der SHA-1-Fingerabdruck ist wie eine eindeutige Schlüsselkarte, die mit Ihrem physischen Schlüssel verknüpft ist. Es handelt sich um einen bestimmten Code, der diesen Schlüssel identifiziert.
        • Die OAuth 2.0-Client-ID ist wie ein Zugangscode für eine bestimmte Google-Ressource oder einen bestimmten Google-Dienst (z.B. Google-Anmeldung).
        • Wenn Sie den SHA-1-Fingerabdruck bei der Einrichtung des OAuth-Clients angeben, teilen Sie Google im Grunde mit: „Nur die Schlüsselkarte mit dieser bestimmten ID (SHA-1) kann diesen Zugangscode (Client-ID) öffnen.“ So wird sichergestellt, dass nur Ihre App auf die mit diesem Zugangscode verknüpften Google-Dienste zugreifen kann.“

Für den Webclient benötigen wir nur den Namen, den Sie zum Identifizieren des Clients in der Console verwenden möchten.

Android-OAuth 2.0-Client erstellen

  1. Rufen Sie die Seite Clients auf.GCP-Clients
  2. Klicken Sie auf Client erstellen.GCP-Clients erstellen
  3. Wählen Sie als Anwendungstyp Android aus.
  4. Sie müssen den Paketnamen Ihrer App angeben.
  5. In Android Studio müssen wir die SHA-1-Signatur unserer App abrufen und hier einfügen:
    1. Rufen Sie Android Studio auf und öffnen Sie das Terminal.
    2. Führen Sie folgenden Befehl aus:
      keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
      
      Dieser Befehl dient dazu, die Details eines bestimmten Eintrags (Alias) in einem Schlüsselspeicher aufzulisten.
      • -list: Mit dieser Option wird keytool angewiesen, den Inhalt des Keystores aufzulisten.
      • -v: Diese Option aktiviert die ausführliche Ausgabe und liefert detailliertere Informationen zum Eintrag.
      • -keystore ~/.android/debug.keystore: Gibt den Pfad zur Keystore-Datei an.
      • -alias androiddebugkey: Gibt den Alias (Eintragsname) des Schlüssels an, den Sie prüfen möchten.
      • -storepass android: Damit wird das Passwort für die Schlüsselspeicherdatei angegeben.
      • -keypass android: Das Passwort für den privaten Schlüssel des angegebenen Alias.
    3. Kopieren Sie den Wert der SHA-1-Signatur:
    SHA-Signatur
    1. Kehren Sie zum Google Cloud-Fenster zurück und fügen Sie den SHA-1-Signaturwert ein:
  6. Ihr Bildschirm sollte jetzt so aussehen. Klicken Sie auf Erstellen:Android-ClientdetailsAndroid-Client

OAuth 2.0-Webclient erstellen

  1. Wenn Sie eine Client-ID für eine Webanwendung erstellen möchten, wiederholen Sie die Schritte 1 bis 2 aus dem Abschnitt Android-Client erstellen und wählen Sie Webanwendung als Anwendungstyp aus.
  2. Geben Sie dem Client einen Namen (dies ist der OAuth-Client): Webclient-Details
  3. Klicken Sie auf Erstellen.Webclient
  4. Kopieren Sie die Client-ID aus dem Pop-up-Fenster. Sie benötigen sie später.Client-ID kopieren

Nachdem wir unsere OAuth-Clients eingerichtet haben, können wir zu Android Studio zurückkehren und unsere Android-App für „Über Google anmelden“ erstellen.

4. Virtuelles Android-Gerät einrichten

Wenn Sie Ihre Anwendung schnell testen möchten, ohne ein physisches Android-Gerät zu verwenden, sollten Sie ein virtuelles Android-Gerät erstellen, auf dem Sie Ihre App direkt in Android Studio erstellen und ausführen können. Wenn Sie mit einem physischen Android-Gerät testen möchten, können Sie der Android-Entwicklerdokumentation folgen.

Android-Virtual Device erstellen

  1. Öffnen Sie in Android Studio den Geräte-Manager Geräte-Manager.
  2. Klicken Sie auf das Pluszeichen (+) > „Virtuelles Gerät erstellen“.Virtuelles Gerät erstellen
  3. Hier können Sie alle Geräte hinzufügen, die Sie für Ihr Projekt benötigen. Wählen Sie für dieses Codelab Medium Phone (Mittelgroßes Smartphone) aus und klicken Sie auf Next (Weiter).Mittelgroßes Smartphone
  4. Sie können das Gerät jetzt für Ihr Projekt konfigurieren, indem Sie ihm einen eindeutigen Namen geben, die Android-Version auswählen, auf der das Gerät ausgeführt werden soll, und vieles mehr. Achten Sie darauf, dass die API auf API 36 „Baklava“; Android 16 eingestellt ist, und klicken Sie dann auf Fertigstellen.Virtuelles Gerät konfigurieren
  5. Das neue Gerät sollte im Geräte-Manager angezeigt werden. Klicken Sie auf Gerät ausführen neben dem soeben erstellten GerätGerät 2 ausführen, um zu prüfen, ob es funktioniert.
  6. Das Gerät sollte jetzt funktionieren.Laufgerät

Im virtuellen Android-Gerät anmelden

Das Gerät, das Sie gerade erstellt haben, funktioniert. Um Fehler beim Testen der Anmeldung mit Google zu vermeiden, müssen Sie sich jetzt mit einem Google-Konto auf dem Gerät anmelden.

  1. Gehen Sie zu den Einstellungen:
    1. Klicken Sie auf die Mitte des Displays des virtuellen Geräts und wischen Sie nach oben.
    Klicken und wischen
    1. Suchen Sie nach der App „Einstellungen“ und klicken Sie darauf.
    Einstellungen
  2. Klicken Sie in den Einstellungen auf GoogleGoogle-Dienste & ‑Einstellungen.
  3. Klicken Sie auf Anmelden und folgen Sie der Anleitung, um sich in Ihrem Google-Konto anzumelden.Geräteanmeldung
  1. Sie sollten jetzt auf dem GerätAuf dem Gerät angemeldet angemeldet sein.

Ihr virtuelles Android-Gerät ist jetzt bereit für Tests.

5. Abhängigkeiten hinzufügen

Dauer: 5:00

Damit wir OAuth-API-Aufrufe ausführen können, müssen wir zuerst die erforderlichen Bibliotheken einbinden, mit denen wir Authentifizierungsanfragen stellen und Google-IDs für diese Anfragen verwenden können:

  • libs.googleid
  • libs.play.services.auth
  1. Wählen Sie „File“ > „Project Structure“ aus:Projektstruktur
  2. Klicken Sie dann auf Dependencies > app > + > Library DependencyAbhängigkeiten
  3. Jetzt müssen wir unsere Bibliotheken hinzufügen:
    1. Geben Sie im Suchdialogfeld googleid ein und klicken Sie auf Suchen.
    2. Es sollte nur ein Eintrag vorhanden sein. Wählen Sie ihn und die höchste verfügbare Version aus (zum Zeitpunkt dieses Codelabs ist es 1.1.1).
    3. Klicken Sie auf OK.Google ID-Paket
    4. Wiederholen Sie die Schritte 1 bis 3, suchen Sie aber stattdessen nach play-services-auth und wählen Sie die Zeile mit „com.google.android.gms“ als Gruppen-ID und „play-services-auth“ als Artefaktnamen aus.Play-Dienste-Authentifizierung
  4. Klicken Sie auf OK.Abgeschlossene Abhängigkeiten

6. Ablauf der Ansicht am unteren Rand

Ablauf der Ansicht am unteren Rand

Beim Bottom-Sheet-Ablauf wird die Credential Manager API verwendet, damit Nutzer sich auf Android-Geräten ganz einfach mit ihren Google-Konten in Ihrer App anmelden können. Das Verfahren ist schnell und bequem, insbesondere für wiederkehrende Nutzer. Dieser Ablauf sollte beim Start der App ausgelöst werden.

Anmeldung erstellen

  1. Entfernen Sie zuerst die Funktionen Greeting() und GreetingPreview() aus MainActivity.kt, da wir sie nicht benötigen.
  2. Jetzt müssen wir dafür sorgen, dass die benötigten Pakete für dieses Projekt importiert werden. Fügen Sie die folgenden import-Anweisungen nach den vorhandenen ab Zeile 3 hinzu:
    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. Als Nächstes müssen wir unsere Funktion zum Erstellen der Bottom Sheet-Anfrage erstellen. Fügen Sie diesen Code unter der MainActivity-Klasse ein.
   //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)
   }

Sehen wir uns an, was dieser Code bewirkt:

fun BottomSheet(webClientId: String) {...}: Erstellt eine Funktion namens „BottomSheet“, die ein Stringargument namens „webClientid“ akzeptiert.

  • val context = LocalContext.current: Ruft den aktuellen Android-Kontext ab. Dies ist für verschiedene Vorgänge erforderlich, z. B. zum Starten von UI-Komponenten.
  • LaunchedEffect(Unit) { ... }: LaunchedEffect ist eine Jetpack Compose-Composable-Funktion, mit der Sie eine suspend-Funktion (eine Funktion, die die Ausführung anhalten und fortsetzen kann) innerhalb des Lebenszyklus der Composable-Funktion ausführen können. Die Einheit als Schlüssel bedeutet, dass dieser Effekt nur einmal ausgeführt wird, wenn das Composable zum ersten Mal gestartet wird.
    • val googleIdOption: GetGoogleIdOption = ...: Erstellt ein GetGoogleIdOption-Objekt. Mit diesem Objekt wird der Typ der Anmeldedaten konfiguriert, die von Google angefordert werden.
      • .Builder(): Ein Builder-Muster wird verwendet, um die Optionen zu konfigurieren.
      • .setFilterByAuthorizedAccounts(true): Gibt an, ob der Nutzer alle Google-Konten oder nur die Konten auswählen darf, für die die App bereits autorisiert wurde. In diesem Fall ist der Wert auf „true“ gesetzt. Das bedeutet, dass die Anfrage mit Anmeldedaten gestellt wird, die der Nutzer zuvor für die Verwendung mit dieser App autorisiert hat, sofern solche Anmeldedaten verfügbar sind.
      • .setServerClientId(webClientId): Legt die Server-Client-ID fest, die eine eindeutige Kennung für das Backend Ihrer App ist. Dies ist erforderlich, um ein ID-Token zu erhalten.
      • .setNonce(generateSecureRandomNonce()): Legt eine Nonce (einen zufälligen Wert) fest, um Replay-Angriffe zu verhindern und dafür zu sorgen, dass das ID-Token mit der jeweiligen Anfrage verknüpft ist.
      • .build(): Erstellt das GetGoogleIdOption-Objekt mit der angegebenen Konfiguration.
    • val request: GetCredentialRequest = ...: Erstellt ein GetCredentialRequest-Objekt. Dieses Objekt kapselt die gesamte Anmeldedatenanfrage.
      • .Builder(): Startet das Builder-Muster zum Konfigurieren der Anfrage.
      • .addCredentialOption(googleIdOption): Fügt der Anfrage die googleIdOption hinzu, um anzugeben, dass wir ein Google-ID-Token anfordern möchten.
      • .build(): Erstellt das GetCredentialRequest-Objekt.
    • val e = signIn(request, context): Hier wird versucht, den Nutzer mit der erstellten Anfrage und dem aktuellen Kontext anzumelden. Das Ergebnis der signIn-Funktion wird in „e“ gespeichert. Diese Variable enthält entweder das erfolgreiche Ergebnis oder eine Ausnahme.
    • if (e is NoCredentialException) { ... }: Dies ist eine bedingte Prüfung. Wenn die signIn-Funktion mit einer NoCredentialException fehlschlägt, bedeutet das, dass keine zuvor autorisierten Konten verfügbar sind.
      • val googleIdOptionFalse: GetGoogleIdOption = ...: Wenn der vorherige signIn fehlgeschlagen ist, wird in diesem Teil ein neuer GetGoogleIdOption erstellt.
      • .setFilterByAuthorizedAccounts(false): Das ist der entscheidende Unterschied zur ersten Option. Dadurch wird das Filtern autorisierter Konten deaktiviert. Das bedeutet, dass sich jeder Nutzer mit einem beliebigen Google-Konto auf dem Gerät anmelden kann.
      • val requestFalse: GetCredentialRequest = ...: Ein neuer GetCredentialRequest wird mit dem googleIdOptionFalse erstellt.
      • signIn(requestFalse, context): Hier wird versucht, den Nutzer mit der neuen Anfrage anzumelden, mit der jedes Konto verwendet werden kann.

Im Wesentlichen wird mit diesem Code eine Anfrage an die Credential Manager API vorbereitet, um ein Google-ID-Token für den Nutzer abzurufen. Dabei werden die bereitgestellten Konfigurationen verwendet. Mit dem GetCredentialRequest kann dann die Benutzeroberfläche des Anmeldeinformationsmanagers gestartet werden, in der der Nutzer sein Google-Konto auswählen und die erforderlichen Berechtigungen erteilen kann.

fun generateSecureRandomNonce(byteLength: Int = 32): String: Damit wird eine Funktion mit dem Namen generateSecureRandomNonce definiert. Es wird ein Ganzzahlargument „byteLength“ (mit dem Standardwert 32) akzeptiert, das die gewünschte Länge der Nonce in Byte angibt. Es wird ein String zurückgegeben, der die Base64-codierte Darstellung der zufälligen Bytes ist.

  • val randomBytes = ByteArray(byteLength): Erstellt ein Byte-Array der angegebenen byteLength, um die zufälligen Byte aufzunehmen.
  • SecureRandom.getInstanceStrong().nextBytes(randomBytes):
    • SecureRandom.getInstanceStrong(): Dadurch wird ein kryptografisch starker Zufallszahlgenerator abgerufen. Das ist aus Sicherheitsgründen entscheidend, da so sichergestellt wird, dass die generierten Zahlen wirklich zufällig und nicht vorhersagbar sind. Dabei wird die stärkste verfügbare Entropiequelle im System verwendet.
    • .nextBytes(randomBytes): Dadurch wird das Array „randomBytes“ mit zufälligen Bytes gefüllt, die von der SecureRandom-Instanz generiert wurden.
  • return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes):
    • Base64.getUrlEncoder(): Hiermit wird ein Base64-Encoder abgerufen, der ein URL-sicheres Alphabet verwendet (mit „-“ und „_“ anstelle von „+“ und „/“). Das ist wichtig, weil der resultierende String so sicher in URLs verwendet werden kann, ohne dass eine weitere Codierung erforderlich ist.
    • .withoutPadding(): Dadurch werden alle Füllzeichen aus dem Base64-codierten String entfernt. Das ist oft wünschenswert, um die Nonce etwas kürzer und kompakter zu machen.
    • .encodeToString(randomBytes): Dadurch werden die randomBytes in einen Base64-String codiert und zurückgegeben.

Zusammenfassend lässt sich sagen, dass diese Funktion eine kryptografisch starke zufällige Nonce einer bestimmten Länge generiert, sie mit URL-sicherem Base64 codiert und den resultierenden String zurückgibt. Dies ist ein Standardverfahren zum Generieren von Nounces, die in sicherheitssensiblen Kontexten verwendet werden können.

Anmeldeanfrage stellen

Nachdem wir die Anmeldeanfrage erstellt haben, können wir sie mit Credential Manager für die Anmeldung verwenden. Dazu müssen wir eine Funktion erstellen, die Anmeldeanfragen über den Credential Manager weiterleitet und gleichzeitig häufige Ausnahmen behandelt, die auftreten können.

Dazu können Sie diese Funktion unter der Funktion BottomSheet() einfügen.

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

Sehen wir uns nun an, was der Code hier macht:

suspend fun signIn(request: GetCredentialRequest, context: Context): Exception?: Dadurch wird eine Suspend-Funktion mit dem Namen „signIn“ definiert. Das bedeutet, dass sie pausiert und fortgesetzt werden kann, ohne den Hauptthread zu blockieren.Sie gibt ein Exception? zurück, das null ist, wenn die Anmeldung erfolgreich ist, oder die spezifische Ausnahme, wenn die Anmeldung fehlschlägt.

Sie hat zwei Parameter:

  • request: Ein GetCredentialRequest-Objekt, das die Konfiguration für den abzurufenden Anmeldedatentyp enthält (z.B. Google-ID).
  • context: Der Android-Kontext, der für die Interaktion mit dem System erforderlich ist.

Für den Text der Funktion:

  • val credentialManager = CredentialManager.create(context): Erstellt eine Instanz von CredentialManager, der Hauptschnittstelle für die Interaktion mit der Credential Manager API. So startet die App den Anmeldevorgang.
  • val failureMessage = "Sign in failed!": Definiert einen String (failureMessage), der in einem Toast angezeigt werden soll, wenn die Anmeldung fehlschlägt.
  • var e: Exception? = null: In dieser Zeile wird eine Variable „e“ initialisiert, in der alle Ausnahmen gespeichert werden, die während des Prozesses auftreten können. Sie beginnt mit „null“.
  • delay(250): Führt zu einer Verzögerung von 250 Millisekunden. Dies ist eine Problemumgehung für ein potenzielles Problem, bei dem möglicherweise sofort beim Start der App eine „NoCredentialException“ ausgelöst wird, insbesondere bei Verwendung eines BottomSheet-Ablaufs. So hat das System Zeit, den Anmeldedaten-Manager zu initialisieren.
  • try { ... } catch (e: Exception) { ... }:Ein Try/Catch-Block wird für eine robuste Fehlerbehandlung verwendet. So wird sichergestellt, dass die App nicht abstürzt, wenn während des Anmeldevorgangs ein Fehler auftritt, und dass die Ausnahme ordnungsgemäß behandelt werden kann.
    • val result = credentialManager.getCredential(request = request, context = context): Hier erfolgt der eigentliche Aufruf der Credential Manager API und der Prozess zum Abrufen von Anmeldedaten wird gestartet. Dabei werden die Anfrage und der Kontext als Eingabe verwendet und dem Nutzer wird eine Benutzeroberfläche angezeigt, auf der er Anmeldedaten auswählen kann. Bei Erfolg wird ein Ergebnis mit den ausgewählten Anmeldedaten zurückgegeben. Das Ergebnis dieses Vorgangs, GetCredentialResponse, wird in der Variablen result gespeichert.
    • Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show():Zeigt eine kurze Toast-Meldung an, die angibt, dass die Anmeldung erfolgreich war.
    • Log.i(TAG, "Sign in Successful!"): Loggt eine lustige, erfolgreiche Nachricht in logcat.
    • catch (e: GetCredentialException): Behandelt Ausnahmen vom Typ GetCredentialException. Dies ist eine übergeordnete Klasse für mehrere spezifische Ausnahmen, die während des Abrufs von Anmeldedaten auftreten können.
    • catch (e: GoogleIdTokenParsingException): Behandelt Ausnahmen, die auftreten, wenn beim Parsen des Google-ID-Tokens ein Fehler auftritt.
    • catch (e: NoCredentialException): Verarbeitet die NoCredentialException, die ausgelöst wird, wenn keine Anmeldedaten für den Nutzer verfügbar sind (z.B. wenn er keine gespeichert hat oder kein Google-Konto hat).
      • Wichtig ist, dass diese Funktion die in e gespeicherte Ausnahme zurückgibt, NoCredentialException, sodass der Aufrufer den jeweiligen Fall behandeln kann, wenn keine Anmeldedaten verfügbar sind.
    • catch (e: GetCredentialCustomException): Behandelt benutzerdefinierte Ausnahmen, die vom Anmeldedatenanbieter ausgelöst werden können.
    • catch (e: GetCredentialCancellationException): Verarbeitet die GetCredentialCancellationException, die ausgelöst wird, wenn der Nutzer den Anmeldevorgang abbricht.
    • Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show(): Zeigt eine Toast-Nachricht an, die darauf hinweist, dass die Anmeldung mit der failureMessage fehlgeschlagen ist.
    • Log.e(TAG, "", e): Die Ausnahme wird mit Log.e im Android-Logcat protokolliert. Log.e wird für Fehler verwendet. Dazu gehört auch der Stacktrace der Ausnahme, der bei der Fehlerbehebung helfen kann. Außerdem ist das wütende Emoticon enthalten.
  • return e: Die Funktion gibt die Ausnahme zurück, falls eine abgefangen wurde, oder „null“, wenn die Anmeldung erfolgreich war.

Zusammenfassend lässt sich sagen, dass dieser Code eine Möglichkeit bietet, die Nutzeranmeldung über die Credential Manager API zu verarbeiten, den asynchronen Vorgang zu verwalten, potenzielle Fehler zu behandeln und dem Nutzer über Pop-up-Benachrichtigungen und Logs Feedback zu geben. Außerdem wird die Fehlerbehandlung durch Humor aufgelockert.

Ansicht am unteren Rand in der App implementieren

Jetzt können wir einen Aufruf einrichten, um den BottomSheet-Ablauf in unserer MainActivity-Klasse mit dem folgenden Code und unserer Webanwendungs-Client-ID auszulösen, die wir zuvor aus der Google Cloud Console kopiert haben:

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

Jetzt können wir unser Projekt speichern (Datei > Speichern) und ausführen:

  1. Drücken Sie die Schaltfläche „Ausführen“:Projekt ausführen
  2. Sobald Ihre App im Emulator ausgeführt wird, sollte das BottomSheet für die Anmeldung angezeigt werden. Klicken Sie auf Weiter, um die Anmeldung zu testen.Ansicht am unteren Rand
  3. Es sollte eine Toast-Meldung angezeigt werden, dass die Anmeldung erfolgreich war.Erfolgreiche Ansicht am unteren Rand

7. Ablauf der Schaltfläche

GIF für den Schaltflächenablauf

Mit dem Schaltflächenablauf für „Mit Google anmelden“ können sich Nutzer ganz einfach mit ihrem bestehenden Google-Konto in Ihrer Android-App registrieren oder anmelden. Nutzer sehen diese Schaltfläche, wenn sie das Bottom Sheet schließen oder sich lieber explizit mit ihrem Google-Konto anmelden oder registrieren möchten. Für Entwickler bedeutet das ein reibungsloseres Onboarding und weniger Probleme bei der Registrierung.

Das lässt sich zwar mit einer Standard-Jetpack Compose-Schaltfläche erledigen, wir verwenden aber ein vorab genehmigtes Markensymbol von der Seite Branding-Richtlinien für „Über Google anmelden“.

Markensymbol zum Projekt hinzufügen

  1. ZIP-Datei mit vorab genehmigten Markensymbolen herunterladen
  2. Entpacken Sie die Datei „signin-assest.zip“ aus Ihren Downloads. Das Vorgehen hängt vom Betriebssystem Ihres Computers ab. Sie können jetzt den Ordner „signin-assets“ öffnen und sich die verfügbaren Symbole ansehen. In diesem Codelab verwenden wir signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png.
  3. Datei kopieren
  4. Fügen Sie die Datei in Android Studio in das Projekt unter res > drawable ein. Klicken Sie dazu mit der rechten Maustaste auf den Ordner drawable und dann auf Einfügen. Möglicherweise müssen Sie den Ordner res maximieren, um ihn zu sehen.Drawable
  5. In einem Dialogfeld werden Sie aufgefordert, die Datei umzubenennen und das Verzeichnis zu bestätigen, dem sie hinzugefügt werden soll. Benennen Sie das Asset in „siwg_button.png“ um und klicken Sie dann auf OK.Schaltfläche „Hinzufügen“

Code für den Schaltflächenablauf

In diesem Code wird dieselbe signIn()-Funktion wie für BottomSheet() verwendet, aber GetSignInWithGoogleOption anstelle von GetGoogleIdOption, da bei diesem Ablauf keine auf dem Gerät gespeicherten Anmeldedaten und Passkeys verwendet werden, um Anmeldeoptionen anzuzeigen. Hier ist der Code, den Sie unter der Funktion BottomSheet() einfügen können:

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

So wird der Code aufgeschlüsselt:

fun ButtonUI(webClientId: String): Hiermit wird eine Funktion namens ButtonUI deklariert, die eine webClientId (die Client-ID Ihres Google Cloud-Projekts) als Argument akzeptiert.

val context = LocalContext.current: Ruft den aktuellen Android-Kontext ab. Dies ist für verschiedene Vorgänge erforderlich, z. B. zum Starten von UI-Komponenten.

val coroutineScope = rememberCoroutineScope(): Erstellt einen Coroutine-Bereich. Damit werden asynchrone Aufgaben verwaltet, sodass der Code ausgeführt werden kann, ohne den Hauptthread zu blockieren. rememberCoroutineScope() ist eine zusammensetzbare Funktion aus Jetpack Compose, die einen Bereich bereitstellt, der an den Lebenszyklus der zusammensetzbaren Funktion gebunden ist.

val onClick: () -> Unit = { ... }: Dadurch wird eine Lambda-Funktion erstellt, die ausgeführt wird, wenn auf die Schaltfläche geklickt wird. Die Lambda-Funktion hat folgende Aufgaben:

  • val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(serverClientId = webClientId).setNonce(generateSecureRandomNonce()).build(): In diesem Teil wird ein GetSignInWithGoogleOption-Objekt erstellt. Mit diesem Objekt werden die Parameter für die Anmeldung mit Google angegeben. Es erfordert die webClientId und eine Nonce (eine zufällige Zeichenfolge, die zur Sicherheit verwendet wird).
  • val request: GetCredentialRequest = GetCredentialRequest.Builder().addCredentialOption(signInWithGoogleOption).build(): Damit wird ein GetCredentialRequest-Objekt erstellt. Mit dieser Anfrage werden die Nutzeranmeldedaten über die Anmeldeinformationsverwaltung abgerufen. Mit dem GetCredentialRequest wird das zuvor erstellte GetSignInWithGoogleOption als Option hinzugefügt, um Anmeldedaten für „Über Google anmelden“ anzufordern.
  • coroutineScope.launch { ... }: Ein CoroutineScope zum Verwalten asynchroner Vorgänge (mit Coroutinen).
    • signIn(request, context): Ruft die zuvor definierte Funktion signIn() auf.

Image(...): Damit wird ein Bild mit dem painterResource gerendert, das das Bild R.drawable.siwg_button lädt.

  • Modifier.fillMaxSize().clickable(enabled = true, onClick = onClick):
    • fillMaxSize(): Das Bild füllt den verfügbaren Platz aus.
    • clickable(enabled = true, onClick = onClick): Macht das Bild anklickbar. Wenn darauf geklickt wird, wird die zuvor definierte Lambda-Funktion „onClick“ ausgeführt.

Zusammenfassend lässt sich sagen, dass mit diesem Code eine Schaltfläche „Über Google anmelden“ in einer Jetpack Compose-Benutzeroberfläche eingerichtet wird. Wenn der Nutzer auf die Schaltfläche klickt, wird eine Anmeldedatenanfrage vorbereitet, um den Credential Manager zu starten und dem Nutzer die Anmeldung mit seinem Google-Konto zu ermöglichen.

Jetzt müssen wir die MainActivity-Klasse aktualisieren, damit unsere ButtonUI()-Funktion ausgeführt wird:

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

Jetzt können wir unser Projekt speichern (Datei > Speichern) und ausführen:

  1. Drücken Sie die Schaltfläche „Ausführen“:Projekt ausführen
  2. Sobald die App auf dem Emulator ausgeführt wird, sollte das BottomSheet angezeigt werden. Klicken Sie auf eine Stelle außerhalb des Fensters, um es zu schließen.Hier tippen
  3. Die von uns erstellte Schaltfläche sollte jetzt in der App angezeigt werden. Klicken Sie darauf, um das Anmeldedialogfeld aufzurufen.Anmeldedialogfeld
  4. Klicken Sie auf Ihr Konto, um sich anzumelden.

8. Fazit

Sie haben dieses Codelab abgeschlossen. Weitere Informationen oder Hilfe zur Anmeldung über Google auf Android-Geräten finden Sie unten im Abschnitt „Häufig gestellte Fragen“:

Häufig gestellte Fragen

Vollständiger Code für „MainActivity.kt“

Hier ist der vollständige Code für „MainActivity.kt“ als Referenz:

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
}