Wdrażanie funkcji Zaloguj się przez Google w aplikacji na Androida

1. Zanim zaczniesz

Dowiedz się, jak wdrożyć funkcję Zaloguj się przez Google na Androidzie za pomocą Credential Manager.

Wymagania wstępne

  • Podstawowa znajomość języka Kotlin na potrzeby tworzenia aplikacji na Androida.
  • Podstawowa znajomość Jetpack Compose (więcej informacji).

Czego się nauczysz

  • Utwórz projekt w chmurze Google Cloud i klienty OAuth.
  • Wdróż proces logowania za pomocą planszy dolnej.
  • Zaimplementuj proces logowania za pomocą przycisku.

Wymagania

2. Tworzenie projektu w Android Studio

Aby rozpocząć, utwórz nowy projekt w Android Studio:

  1. Otwórz Android Studio i kliknij Nowy projekt. Powitanie w Android Studio
  2. Kliknij kolejno Telefon i tablet > Pusta aktywnośćDalej. Projekt Android Studio
  3. Skonfiguruj ustawienia projektu:
    • Nazwa: wybierz nazwę projektu.
    • Nazwa pakietu: użyj domyślnej nazwy lub wybierz własną.
    • Minimalny pakiet SDK: wybierz najnowszą stabilną wersję lub > Android 14.

Projekt konfiguracji Android Studio

  1. Kliknij Zakończ i poczekaj na zakończenie początkowej kompilacji projektu. Projekt Android Studio został skompilowany

3. Konfigurowanie projektu Google Cloud

Tworzenie projektu Google Cloud

  1. Otwórz konsolę Google Cloud i wybierz lub utwórz projekt. GCP create new project
  2. Otwórz Interfejsy API i usługi > Ekran zgody OAuth. Ekran zgody OAuth w GCP
  3. Kliknij Rozpocznij i wypełnij wymagane pola:
    • Nazwa aplikacji: użyj nazwy aplikacji na Androida.
    • Adres e-mail pomocy dla użytkowników: wybierz konto Google.
    • Odbiorcy: kliknij Zewnętrzni.
    • Informacje kontaktowe: wpisz swój adres e-mail. Informacje o aplikacji GCP
  4. Zapoznaj się z zasadami dotyczącymi danych użytkownika w usługach interfejsu API Google i kliknij Utwórz. GCP Create

Konfigurowanie klientów OAuth

Aby uzyskać identyfikatory klienta na potrzeby uwierzytelniania, musisz utworzyć klienta internetowegoklienta Androida w konsoli Google Cloud.

  • Klient Androida: zabezpiecza żądania przez weryfikację nazwy pakietu i podpisu SHA-1 aplikacji.
  • Klient internetowy: działa jako klient backendu usługi Logowanie przez Google.

Tworzenie klienta OAuth 2.0 na Androida

  1. Na stronie Klienci kliknij Utwórz klienta i jako Typ aplikacji wybierz Android. GCP Create Clients
  2. Wpisz nazwę pakietu aplikacji (zgodną z wierszem 1 w MainActivity.kt).
  3. Wygeneruj podpis SHA-1. Otwórz terminal Android Studio i uruchom:macOS/Linux:
    keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
    
    Windows:
    keytool -list -v -keystore "C:\Users\USERNAME\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
    
  4. Skopiuj odcisk cyfrowy SHA-1 z wyniku polecenia, wklej go w polu Odcisk cyfrowy SHA-1 w konsoli i kliknij Utwórz. Szczegóły klienta Androida

Tworzenie klienta internetowego OAuth 2.0

  1. Ponownie kliknij Utwórz klienta i jako Typ aplikacji wybierz Aplikacja internetowa.
  2. Nadaj nazwę klientowi internetowemu, pozostaw puste pola URL/Pochodzenie i kliknij Utwórz. Szczegóły klienta internetowego
  3. Skopiuj wygenerowany identyfikator klienta z okna potwierdzenia. Użyjesz go w kodzie Kotlin. Skopiuj identyfikator klienta

4. Konfigurowanie urządzenia wirtualnego z Androidem (AVD)

Aby przetestować aplikację, możesz użyć fizycznego urządzenia z Androidem lub urządzenia wirtualnego z Androidem (AVD).

Tworzenie i uruchamianie AVD

  1. W Android Studio otwórz Menedżera urządzeń, kliknij Utwórz urządzenie wirtualne (lub ikonę +) i wybierz Telefon średniej wielkości.
  2. Wybierz najnowszą stabilną wersję jako obraz systemu i kliknij Zakończ.
  3. Uruchom emulator, klikając ikonę Odtwórz/Uruchom obok urządzenia. Urządzenie do biegania

Logowanie się na konto Google na urządzeniu

  1. W emulatorze otwórz aplikację Ustawienia i kliknij Google.
  2. Kliknij Zaloguj się na konto Google i postępuj zgodnie z instrukcjami. Urządzenie zalogowane

5. Dodawanie zależności

Dodaj do projektu biblioteki wymagane do uwierzytelniania i integracji z Google ID:

  1. Otwórz Plik > Struktura projektu > Zależności > aplikacja.
  2. Kliknij + > Library Dependency (Zależność biblioteki), wyszukaj com.google.android.libraries.identity.googleid:googleid i wybierz najnowszą wersję (np. 1.1.1).
  3. Ponownie kliknij + > Library Dependency (Zależność biblioteki), wyszukaj play-services-auth i wybierz bibliotekę z identyfikatorem grupy com.google.android.gms.
  4. Aby zastosować zmiany i zsynchronizować projekt, kliknij OK. Zakończone zależności

6. Wdrażanie procesu planszy dolnej

Przepływ planszy dolnej

Proces Bottom Sheet korzysta z interfejsu Credential Manager API, aby zapewnić użytkownikom uproszczony sposób logowania się w aplikacji za pomocą kont Google na Androidzie. Ten proces, zaprojektowany z myślą o szybkości i wygodzie, zwłaszcza dla powracających użytkowników, powinien być uruchamiany przy uruchomieniu aplikacji.

Tworzenie żądania logowania

  1. Aby rozpocząć, otwórz MainActivity.kt i usuń domyślne funkcje Greeting()GreetingPreview().
  2. Dodaj te instrukcje importu po dotychczasowych, zaczynając od wiersza 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"
    
  3. Dodaj tę funkcję typu „composable” pod klasą MainActivity w pliku MainActivity.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)
     }
    

Analiza kodu

  • LaunchedEffect(Unit): natychmiast uruchamia proces logowania, gdy element kompozycyjny jest wyświetlany po raz pierwszy.
  • GetGoogleIdOption.Builder(): konfiguruje żądanie tokena identyfikatora Google.
    • setFilterByAuthorizedAccounts(true): najpierw próbuje zalogować się w sposób cichy, filtrując konta, które użytkownik już autoryzował w tej aplikacji, co minimalizuje problemy dla powracających użytkowników.
    • setNonce(...): przekazuje bezpieczny losowy identyfikator nonce wygenerowany dla każdego żądania przez generateSecureRandomNonce(), aby zapobiec atakom typu replay.
  • signIn(request, context): wykonuje żądanie. Jeśli się nie powiedzie (NoCredentialException oznacza, że nie ma wcześniej autoryzowanego konta), proces wraca do setFilterByAuthorizedAccounts(false), aby umożliwić użytkownikowi wybranie dowolnego konta Google zalogowanego na urządzeniu.

Wysyłanie prośby o zalogowanie

Po utworzeniu prośby o zalogowanie możesz użyć Menedżera danych logowania, aby dokończyć proces logowania. Utwórz funkcję o nazwie signIn, która wykonuje żądanie i obsługuje typowe wyjątki, które mogą wystąpić.

Dodaj tę funkcję poniżej funkcji BottomSheet w pliku 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
    }
}

Analiza kodu

  • credentialManager.getCredential(...): wywołuje interfejs Credential Manager API, aby wyświetlić planszę dolną lub okno wyboru konta systemowego.
  • delay(250): wstrzymuje działanie na krótko, aby zapobiec sytuacji wyścigu, gdy arkusz u dołu jest wywoływany natychmiast po uruchomieniu aplikacji, zanim usługa Credential Manager zakończy inicjowanie.
  • Obsługa wyjątków: przechwytuje i rejestruje typowe błędy związane z danymi logowania (np. anulowanie, brak danych logowania lub problemy z parsowaniem tokena) oraz wyświetla użytkownikowi powiadomienia.

Uruchamianie przepływu planszy dolnej

Zaktualizuj klasę MainActivity, aby wywoływać BottomSheet() podczas uruchamiania. Zastąp YOUR_CLIENT_ID_HERE identyfikatorem klienta aplikacji internetowej:

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

Zapisz projekt (Plik > Zapisz) i uruchom aplikację:

  1. Naciśnij przycisk uruchamiania:Uruchom projekt
  2. Gdy aplikacja zostanie uruchomiona na emulatorze, powinien pojawić się arkusz z prośbą o zalogowanie. Aby przetestować przepływ, kliknij Dalej.Plansza dolna
  3. Powinno pojawić się powiadomienie potwierdzające, że logowanie się powiodło.Plansza dolna z potwierdzeniem

7. Wdrażanie automatyzacji przycisku

GIF z przepływem przycisku

Przepływ przycisku zapewnia użytkownikom wyraźną opcję zalogowania się lub zarejestrowania. Używanie standardowych elementów marki zapewnia spójność informacji. Używaj wstępnie zatwierdzonych zasobów, które są zgodne z wytycznymi dotyczącymi promowania marki Zaloguj się przez Google.

Dodawanie ikony marki

  1. Pobierz zasoby marki tutaj i wyodrębnij plik ZIP.
  2. Kopiuj signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png.
  3. W Android Studio wklej plik do folderu res > drawable, zmień jego nazwę na siwg_button.png i kliknij OK. Dodawanie przycisku

Kod przepływu przycisku

Ten proces ponownie wykorzystuje tę samą funkcję pomocniczą signIn, ale przekazuje GetSignInWithGoogleOption zamiast GetGoogleIdOption. W przeciwieństwie do przepływu Bottom Sheet przepływ z wyraźnym przyciskiem nie filtruje wstępnie ani nie wyświetla automatycznie prośby o podanie zapisanych danych logowania lub kluczy dostępu. Wklej tę funkcję typu „composable” pod funkcją 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)
    )
}

Analiza kodu

  • GetSignInWithGoogleOption: w przeciwieństwie do przepływu arkusza u dołu przepływ z wyraźnym przyciskiem używa tej opcji, aby poprosić użytkowników o wybranie konta Google bez automatycznego filtrowania.
  • coroutineScope.launch: uruchamia współprogram, aby asynchronicznie wykonać funkcję zawieszania signIn po kliknięciu przycisku.
  • Image: wyświetla element rysowalny siwg_button z logo marki i dołącza odbiornik kliknięć, aby wywołać przepływ.

Dodawanie przycisku do układu interfejsu

Zaktualizuj układ MainActivity, aby wyświetlać automatyczne BottomSheet i jawne ButtonUI wyrównane pionowo:

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

Testowanie automatyzacji przycisku

  1. Uruchom aplikację.
  2. Zamknij początkową planszę dolną, klikając poza jej obszarem.
  3. Kliknij przycisk Zaloguj się przez Google, aby otworzyć okno logowania, i wybierz swoje konto. Okno logowania
  4. Sprawdź wynik: w Logcat potwierdź wydruk nazwy użytkownika lub adresu e-mail.

8. Podsumowanie

Gratulacje! Udało Ci się wdrożyć logowanie się przez Google za pomocą Menedżera danych logowania na Androidzie.

Dodatkowe materiały

Pełny kod pliku MainActivity.kt

Oto pełny kod pliku MainActivity.kt:

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