Узнайте, как реализовать вход через Google в вашем Android-приложении.

1. Прежде чем начать

Узнайте, как реализовать вход через Google на Android с помощью Credential Manager.

Предварительные требования

  • Базовое понимание Kotlin для разработки под Android.
  • Базовое понимание Jetpack Compose ( подробнее ).

Что вы узнаете

  • Создайте проект Google Cloud и клиенты OAuth.
  • Реализуйте процедуру входа через нижний интерфейс.
  • Реализуйте явный процесс авторизации с помощью кнопки.

Что вам нужно

2. Создайте проект в Android Studio.

Для начала создайте новый проект в Android Studio:

  1. Откройте Android Studio и нажмите «Новый проект» . Добро пожаловать в Android Studio!
  2. Выберите «Телефон и планшет» > «Очистить активность» , затем нажмите «Далее» .Проект Android Studio
  3. Настройте параметры проекта:
    • Название : Выберите название проекта.
    • Название пакета : Используйте название по умолчанию или выберите своё.
    • Минимальный уровень SDK : выберите самую последнюю стабильную версию или Android 14 или выше .

Настройка проекта в Android Studio

  1. Нажмите «Готово» и дождитесь завершения первоначальной сборки проекта.Проект Android Studio создан

3. Настройте свой проект в Google Cloud.

Создайте проект в Google Cloud.

  1. Перейдите в консоль Google Cloud и выберите или создайте проект. GCP создает новый проект
  2. Перейдите в раздел API и сервисы > Экран согласия OAuth . Экран согласия GCP OAuth
  3. Нажмите «Начать» и заполните необходимые поля:
    • Название приложения : Используйте название вашего приложения для Android.
    • Адрес электронной почты службы поддержки пользователей : выберите свой аккаунт Google.
    • Целевая аудитория : выберите «Внешняя» .
    • Контактная информация : Введите свой адрес электронной почты. Информация о приложении GCP
  4. Ознакомьтесь с политикой Google API Services: User Data Policy и нажмите «Создать» .GCP Create

Настройка клиентов OAuth

Для получения идентификаторов клиентов для аутентификации необходимо создать как веб-клиент , так и Android-клиент в консоли Google Cloud.

  • Клиент для Android : обеспечивает безопасность запросов путем проверки имени пакета вашего приложения и подписи SHA-1.
  • Веб-клиент : выступает в качестве серверной части для сервиса авторизации Google.

Создание Android OAuth 2.0 клиента

  1. На странице «Клиенты» нажмите «Создать клиент» и выберите Android в качестве типа приложения .Создание клиентов в GCP
  2. Введите имя пакета вашего приложения (соответствующее строке 1 файла MainActivity.kt ).
  3. Сгенерируйте свою подпись SHA-1. Откройте терминал Android Studio и выполните команду: macOS/Linux:
    keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
    
    Окна:
    keytool -list -v -keystore "C:\Users\USERNAME\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
    
  4. Скопируйте отпечаток SHA-1 из вывода команды, вставьте его в поле «Отпечаток SHA-1» в консоли и нажмите «Создать» . Сведения о клиенте Android

Создание клиента Web OAuth 2.0

  1. Снова нажмите «Создать клиента» и выберите «Веб-приложение» в качестве типа приложения .
  2. Назовите свой веб-клиент, оставьте поля URL/Origins пустыми и нажмите «Создать» .Сведения о веб-клиенте
  3. Скопируйте сгенерированный идентификатор клиента из диалогового окна подтверждения. Вы будете использовать его в своем коде Kotlin. Скопировать идентификатор клиента

4. Настройка виртуального устройства Android

Для тестирования приложения можно использовать физическое устройство Android или виртуальное устройство Android (AVD).

Создайте и запустите AVD.

  1. В Android Studio откройте Диспетчер устройств , нажмите «Создать виртуальное устройство» (или значок «+» ) и выберите «Средний телефон» .
  2. Выберите самую последнюю стабильную версию в качестве образа системы и нажмите «Готово» .
  3. Запустите эмулятор, нажав на значок «Играть/Запустить» рядом с устройством. Бегущее устройство

Войдите в учетную запись Google на устройстве.

  1. В эмуляторе откройте приложение «Настройки» и перейдите в раздел Google .
  2. Нажмите «Войти в свой аккаунт Google» и следуйте инструкциям. Устройство авторизовано

5. Добавьте зависимости

Добавьте в свой проект библиотеки, необходимые для аутентификации и интеграции с Google ID:

  1. Перейдите в меню Файл > Структура проекта > Зависимости > app .
  2. Нажмите + > Зависимости библиотеки , найдите com.google.android.libraries.identity.googleid:googleid и выберите последнюю версию (например, 1.1.1 ).
  3. Снова нажмите + > Зависимость библиотеки , найдите play-services-auth и выберите библиотеку с идентификатором группы com.google.android.gms .
  4. Нажмите ОК , чтобы применить изменения и синхронизировать проект. Завершенные зависимости

6. Реализуйте алгоритм построения нижней панели.

Поток нижнего слоя

Функция «Нижний экран» использует API диспетчера учетных данных , чтобы обеспечить пользователям удобный способ входа в ваше приложение с помощью своих учетных записей Google на Android . Разработанная для скорости и удобства, особенно для постоянных пользователей, эта функция должна запускаться при запуске приложения.

Сформировать запрос на вход в систему.

  1. Для начала откройте MainActivity.kt и удалите функции Greeting() и GreetingPreview() по умолчанию.
  2. Добавьте следующие операторы импорта после существующих, начиная со строки 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. Добавьте эту составную функцию ниже класса MainActivity в файле 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)
     }
    

Разбор кода

  • LaunchedEffect(Unit) : Запускает процесс авторизации немедленно при первом отображении объекта Composable.
  • GetGoogleIdOption.Builder() : Настраивает запрос токена Google ID.
    • setFilterByAuthorizedAccounts(true) : Сначала выполняется попытка бесшумного входа в систему путем фильтрации учетных записей, которые пользователь уже авторизовал для этого приложения, что минимизирует неудобства для повторно использующих приложение пользователей.
    • setNonce(...) : Передает безопасный случайный nonce, генерируемый для каждого запроса функцией generateSecureRandomNonce() для предотвращения атак повторного воспроизведения.
  • signIn(request, context) : Выполняет запрос. Если он завершается ошибкой NoCredentialException (что означает отсутствие ранее авторизованной учетной записи), процесс переключается на setFilterByAuthorizedAccounts(false) , позволяя пользователю выбрать любую учетную запись Google, авторизованную на устройстве.

Отправьте запрос на вход в систему.

После создания запроса на вход в систему вы можете использовать диспетчер учетных данных для завершения процесса авторизации. Создайте функцию с именем signIn , которая выполняет запрос и обрабатывает распространенные исключения, которые могут возникнуть.

Добавьте эту функцию ниже функции BottomSheet в файле 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
    }
}

Разбор кода

  • credentialManager.getCredential(...) : Вызывает API диспетчера учетных данных для отображения нижней панели или диалогового окна выбора системной учетной записи.
  • delay(250) : Кратковременная пауза для предотвращения состояния гонки, когда нижний экран запускается сразу после запуска приложения, до того, как служба диспетчера учетных данных завершит инициализацию.
  • Обработка исключений : перехватывает и регистрирует распространенные ошибки учетных данных (например, отмена, отсутствие учетных данных или проблемы с анализом токена) и предоставляет пользователю обратную связь в виде всплывающих уведомлений.

Запустить поток нижнего слоя

Обновите класс MainActivity , чтобы он вызывал BottomSheet() при запуске. Замените YOUR_CLIENT_ID_HERE на идентификатор клиента вашего веб-приложения:

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

Сохраните свой проект ( Файл > Сохранить ) и запустите приложение:

  1. Нажмите кнопку запуска:Запуск проекта
  2. При запуске приложения на эмуляторе должно появиться всплывающее окно входа в систему. Нажмите «Продолжить» , чтобы проверить процесс.Нижний лист
  3. Должно появиться всплывающее уведомление, подтверждающее успешный вход в систему. Успех нижнего листа

7. Реализуйте последовательность действий с помощью кнопок.

Button Flow GIF

Кнопка предоставляет пользователям явную возможность войти в систему или зарегистрироваться. Использование стандартного фирменного стиля обеспечивает единообразие пользовательского опыта. Используйте предварительно одобренные элементы, соответствующие рекомендациям по фирменному стилю «Вход через Google» .

Добавьте значок бренда

  1. Скачайте фирменные элементы здесь и распакуйте ZIP-архив.
  2. Copy signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png .
  3. В Android Studio скопируйте файл в папку res > drawable , переименуйте его в siwg_button.png и нажмите OK . Кнопка добавления

Код обработки нажатия кнопки

В этом потоке используется та же вспомогательная функция signIn , но вместо GetGoogleIdOption передается GetSignInWithGoogleOption . В отличие от потока Bottom Sheet, поток с явной кнопкой не выполняет предварительную фильтрацию и не запрашивает автоматически сохраненные учетные данные или пароли. Вставьте эту составную функцию ниже функции 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)
    )
}

Разбор кода

  • GetSignInWithGoogleOption : В отличие от сценария с нижней панелью инструментов, сценарий с явной кнопкой использует эту опцию, чтобы предложить пользователям выбрать свою учетную запись Google без автоматической фильтрации.
  • coroutineScope.launch : Запускает сопрограмму для асинхронного выполнения функции suspend signIn при нажатии кнопки.
  • Image : Отображает фирменный рисунок siwg_button и добавляет обработчик клика для запуска процесса.

Добавьте кнопку в макет пользовательского интерфейса.

Обновите макет MainActivity , чтобы отображать как автоматически созданный BottomSheet , так и явно созданный ButtonUI с вертикальным выравниванием:

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

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

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

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

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

Проверьте работу кнопок.

  1. Запустите приложение.
  2. Чтобы закрыть нижний лист, щелкните за пределами его области.
  3. Нажмите кнопку «Войти через Google» , чтобы открыть диалоговое окно входа, и выберите свою учетную запись.Диалог входа
  4. Проверьте результат: проверьте Logcat, чтобы убедиться в наличии вашего имени пользователя/адреса электронной почты.

8. Заключение

Поздравляем! Вы успешно реализовали вход через Google с помощью Android Credential Manager.

Дополнительные ресурсы

Полный код файла MainActivity.kt

Для справки, вот полный код файла 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
    }
}