Aprende a implementar Acceder con Google en tu app para Android

1. Antes de comenzar

Aprende a implementar Acceder con Google en Android con el Administrador de credenciales.

Requisitos previos

Qué aprenderás

  • Crea un proyecto de Google Cloud y clientes de OAuth.
  • Implementa el flujo de acceso de la hoja inferior.
  • Implementa el flujo de acceso del botón explícito.

Requisitos

2. Crea un proyecto de Android Studio

Para comenzar, crea un proyecto nuevo en Android Studio:

  1. Abre Android Studio y haz clic en New Project. Bienvenido a Android Studio
  2. Selecciona Phone and Tablet > Empty Activity y, luego, haz clic en Next. Proyecto de Android Studio
  3. Configura la configuración del proyecto:
    • Nombre: Elige un nombre para el proyecto.
    • Nombre del paquete: Usa el valor predeterminado o elige uno propio.
    • Minimum SDK: Selecciona la versión estable más reciente o > Android 14.

Proyecto de configuración de Android Studio

  1. Haz clic en Finish y espera a que se complete la compilación inicial del proyecto. Proyecto de Android Studio compilado

3. Configura el proyecto de Google Cloud

Crea un proyecto de Google Cloud

  1. Ve a la consola de Google Cloud y selecciona o crea un proyecto. Crea un proyecto nuevo en GCP
  2. Navega a APIs & Services > pantalla de consentimiento de OAuth. Pantalla de consentimiento de OAuth de GCP
  3. Haz clic en Get started y completa los campos obligatorios:
    • Nombre de la app: Usa el nombre de tu app para Android.
    • Correo electrónico de asistencia al usuario: Selecciona tu Cuenta de Google.
    • Audience: Selecciona External.
    • Contact Info: Ingresa tu dirección de correo electrónico. Información de la app de GCP
  4. Revisa la Política de Datos del Usuario de los Servicios de las APIs de Google y haz clic en Create. Creación de GCP

Configura clientes de OAuth

Debes crear un cliente web y un cliente de Android en la consola de Google Cloud para obtener los IDs de cliente para la autenticación.

  • Cliente de Android: Protege las solicitudes verificando el nombre del paquete y la firma SHA-1 de tu app.
  • Cliente web: Actúa como el cliente de backend para el servicio de Acceder con Google.

Crea un cliente de OAuth 2.0 para Android

  1. En la página Clientes, haz clic en Crear cliente y selecciona Android como el Tipo de aplicación. Crea clientes de GCP
  2. Ingresa el nombre del paquete de tu app (que coincida con la línea 1 de tu MainActivity.kt).
  3. Genera tu firma SHA-1. Abre la terminal de Android Studio y ejecuta lo siguiente: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. Copia la huella digital SHA-1 del resultado del comando, pégala en el campo Huella digital SHA-1 de la consola y haz clic en Crear. Detalles del cliente de Android

Crea un cliente de OAuth 2.0 para la Web

  1. Vuelve a hacer clic en Crear cliente y selecciona Aplicación web como el Tipo de aplicación.
  2. Ponle un nombre a tu cliente web, deja en blanco los campos de URL/orígenes y haz clic en Crear. Detalles del cliente web
  3. Copia el ID de cliente generado del diálogo de confirmación. Lo usarás en tu código Kotlin. Copiar ID de cliente

4. Configura un dispositivo virtual de Android

Para probar la app, puedes usar un dispositivo Android físico o un dispositivo virtual de Android (AVD).

Crea y ejecuta el AVD

  1. En Android Studio, abre el Administrador de dispositivos, haz clic en Create Virtual Device (o en el ícono +) y selecciona Medium Phone.
  2. Selecciona la versión estable más reciente como la imagen del sistema y haz clic en Finish.
  3. Para iniciar el emulador, haz clic en el ícono Play/Run junto al dispositivo. Dispositivo en ejecución

Accede a una Cuenta de Google en el dispositivo

  1. En el emulador, abre la app de Configuración y ve a Google.
  2. Haz clic en Acceder a tu Cuenta de Google y sigue las instrucciones. Se accedió al dispositivo

5. Cómo agregar dependencias

Agrega las bibliotecas necesarias para la autenticación y la integración del ID de Google a tu proyecto:

  1. Ve a File > Project Structure > Dependencies > app.
  2. Haz clic en + > Library Dependency, busca com.google.android.libraries.identity.googleid:googleid y selecciona la versión más reciente (p.ej., 1.1.1).
  3. Vuelve a hacer clic en + > Library Dependency, busca play-services-auth y selecciona la biblioteca con el ID de grupo com.google.android.gms.
  4. Haz clic en OK para aplicar los cambios y sincronizar tu proyecto. Dependencias finalizadas

6. Implementa el flujo de la hoja inferior

Flujo de la hoja inferior

El flujo de la hoja inferior aprovecha la API de Credential Manager para proporcionar una forma optimizada para que los usuarios accedan a tu app con sus cuentas de Google en Android. Diseñado para brindar velocidad y comodidad, en especial para los usuarios recurrentes, este flujo debe activarse cuando se inicia la app.

Crea la solicitud de acceso

  1. Para comenzar, abre MainActivity.kt y quita las funciones Greeting() y GreetingPreview() predeterminadas.
  2. Agrega las siguientes instrucciones de importación después de las existentes que comienzan en la línea 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. Agrega esta función de componibilidad debajo de la clase MainActivity en tu archivo 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)
     }
    

Desglose del código

  • LaunchedEffect(Unit): Activa el flujo de acceso de inmediato cuando se muestra el elemento componible por primera vez.
  • GetGoogleIdOption.Builder(): Configura la solicitud de token de ID de Google.
    • setFilterByAuthorizedAccounts(true): Primero, intenta un acceso silencioso filtrando las cuentas que el usuario ya autorizó para esta app, lo que minimiza la fricción para los usuarios recurrentes.
    • setNonce(...): Pasa un nonce aleatorio seguro generado para cada solicitud por generateSecureRandomNonce() para evitar ataques de repetición.
  • signIn(request, context): Ejecuta la solicitud. Si falla con una NoCredentialException (lo que significa que no existe una cuenta autorizada anteriormente), el flujo vuelve a setFilterByAuthorizedAccounts(false) para permitir que el usuario seleccione cualquier cuenta de Google con la que haya accedido en el dispositivo.

Realiza la solicitud de acceso

Con la solicitud de acceso creada, puedes usar el Administrador de credenciales para completar el proceso de acceso. Crea una función llamada signIn que ejecute la solicitud y controle las excepciones comunes que puedan ocurrir.

Agrega esta función debajo de la función BottomSheet en tu archivo 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
    }
}

Desglose del código

  • credentialManager.getCredential(...): Llama a la API de Credential Manager para mostrar la hoja inferior o el diálogo del selector de cuentas del sistema.
  • delay(250): Se detiene brevemente para evitar una condición de carrera cuando se activa la hoja inferior inmediatamente al inicio de la app antes de que el servicio de Credential Manager termine de inicializarse.
  • Control de excepciones: Captura y registra errores de credenciales comunes (p.ej., cancelación, falta de credenciales o problemas de análisis de tokens) y proporciona comentarios del usuario con mensajes emergentes.

Activa el flujo de la hoja inferior

Actualiza tu clase MainActivity para llamar a BottomSheet() al inicio. Reemplaza YOUR_CLIENT_ID_HERE por el ID de cliente de tu aplicación web:

class MainActivity : ComponentActivity() {
    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //replace with your own web client ID from Google Cloud Console
        val webClientId = "YOUR_CLIENT_ID_HERE"

        setContent {
            //ExampleTheme - this is derived from the name of the project not any added library
            //e.g. if this project was named "Testing" it would be generated as TestingTheme
            ExampleTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
                ) {
                    //This will trigger on launch
                    BottomSheet(webClientId)
                }
            }
        }
    }
}

Guarda tu proyecto (File > Save) y ejecuta la aplicación:

  1. Haz clic en el botón Ejecutar:Ejecutar proyecto
  2. Cuando se inicie la app en el emulador, debería aparecer la hoja inferior de acceso. Haz clic en Continuar para probar el flujo.Hoja inferior
  3. Debería aparecer una notificación emergente que confirme que el acceso se realizó correctamente.Hoja inferior de éxito

7. Implementa el flujo del botón

GIF del flujo del botón

El flujo del botón proporciona una opción explícita para que los usuarios accedan o se registren. Usar la marca estándar garantiza una experiencia coherente. Usa recursos aprobados previamente que cumplan con los Lineamientos de Desarrollo de la Marca de Acceder con Google.

Agrega el ícono de la marca

  1. Descarga los recursos de la marca aquí y extrae el archivo ZIP.
  2. Copia signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png.
  3. En Android Studio, pega el archivo en la carpeta res > drawable , cambia su nombre a siwg_button.png y haz clic en OK. Botón para agregar

Código del flujo del botón

Este flujo reutiliza la misma función auxiliar signIn, pero pasa un GetSignInWithGoogleOption en lugar de GetGoogleIdOption. A diferencia del flujo de la hoja inferior, el flujo del botón explícito no filtra previamente ni solicita automáticamente credenciales o llaves de acceso almacenadas. Pega esta función de componibilidad debajo de la función 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)
    )
}

Desglose del código

  • GetSignInWithGoogleOption: A diferencia del flujo de la hoja inferior, el flujo del botón explícito usa esta opción para solicitar a los usuarios que seleccionen su Cuenta de Google sin filtrado automático.
  • coroutineScope.launch: Inicia una corrutina para ejecutar la función signIn suspendida de forma asíncrona cuando se hace clic en el botón.
  • Image: Muestra el elemento de diseño siwg_button de la marca y adjunta un objeto de escucha de clics para activar el flujo.

Agrega el botón al diseño de la IU

Actualiza tu diseño MainActivity para mostrar el BottomSheet automático y el ButtonUI explícito alineados verticalmente:

class MainActivity : ComponentActivity() {
    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //replace with your own web client ID from Google Cloud Console
        val webClientId = "YOUR_CLIENT_ID_HERE"

        setContent {
            //ExampleTheme - this is derived from the name of the project not any added library
            //e.g. if this project was named "Testing" it would be generated as TestingTheme
            ExampleTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
                ) {
                    Column(
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally

                    ) {
                        //This will trigger on launch
                        BottomSheet(webClientId)

                        //This requires the user to press the button
                        ButtonUI(webClientId)
                    }
                }
            }
        }
    }
}

Prueba el flujo del botón

  1. Ejecuta la aplicación.
  2. Para descartar la hoja inferior inicial, haz clic fuera del área de la hoja.
  3. Haz clic en el botón Acceder con Google para iniciar el diálogo de acceso y selecciona tu cuenta. Diálogo de acceso
  4. Verifica el resultado: Consulta Logcat para confirmar la impresión de tu nombre de usuario o dirección de correo electrónico.

8. Conclusión

¡Felicitaciones! Implementaste correctamente Acceder con Google con el Administrador de credenciales de Android.

Recursos adicionales

Código completo de MainActivity.kt

Aquí se muestra el código completo de MainActivity.kt como referencia:

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