Android アプリに「Google でログイン」を実装する方法を学習する

1. 始める前に

認証情報マネージャーを使用して Android に「Google でログイン」を実装する方法を学びます。

前提条件

  • Android 開発のための Kotlin に関する基礎知識。
  • Jetpack Compose に関する基礎知識(詳細)。

学習内容

  • Google Cloud プロジェクトと OAuth クライアントを作成します。
  • ボトムシートのログインフローを実装します。
  • 明示的なボタンのログインフローを実装します。

必要なもの

2. Android Studio プロジェクトを作成する

まず、Android Studio で新しいプロジェクトを作成します。

  1. Android Studio を開き、[New Project] をクリックします。Android Studio のウェルカム画面
  2. [Phone and Tablet > Empty Activity] を選択し、[Next] をクリックします。Android Studio Project Marble
  3. プロジェクト設定を構成します。
    • 名前: プロジェクト名を選択します。
    • パッケージ名: デフォルトを使用するか、独自のものを選択します。
    • Minimum SDK: 最新の安定版または > Android 14 を選択します。

Android Studio セットアップ プロジェクト

  1. [Finish] をクリックし、初期プロジェクトのビルドが完了するまで待ちます。Android Studio プロジェクトのビルド

3. Google Cloud プロジェクトを設定する

Google Cloud プロジェクトを作成する

  1. Google Cloud コンソールに移動し、プロジェクトを選択または作成します。GCP で新しいプロジェクトを作成する
  2. [API とサービス] > [OAuth 同意画面] に移動します。GCP OAuth 同意画面
  3. [開始] をクリックし、必須項目を入力します。
    • アプリ名: Android アプリの名前を使用します。
    • ユーザー サポートメール: Google アカウントを選択します。
    • Audience: [External] を選択します。
    • 連絡先情報: メールアドレスを入力します。GCP アプリ情報
  4. Google API サービス: ユーザーデータに関するポリシーを確認し、[作成] をクリックします。GCP Create

OAuth クライアントを設定する

認証用のクライアント ID を取得するには、Google Cloud コンソールでウェブ クライアントAndroid クライアントの両方を作成する必要があります。

  • Android クライアント: アプリのパッケージ名と SHA-1 署名を確認してリクエストを保護します。
  • ウェブ クライアント: Google ログイン サービスのバックエンド クライアントとして機能します。

Android 版 OAuth 2.0 クライアントを作成する

  1. [クライアント] ページで [クライアントを作成] をクリックし、[アプリケーションの種類] として [Android] を選択します。GCP クライアントの作成
  2. アプリのパッケージ名(MainActivity.kt の 1 行目と一致)を入力します。
  3. SHA-1 署名を生成します。Android Studio ターミナルを開き、次のコマンドを実行します。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. コマンド出力から SHA-1 フィンガープリントをコピーし、コンソールの [SHA-1 フィンガープリント] フィールドに貼り付けて、[作成] をクリックします。Android クライアントの詳細

ウェブ OAuth 2.0 クライアントを作成する

  1. [クライアントを作成] をもう一度クリックし、[アプリケーションの種類] として [ウェブ アプリケーション] を選択します。
  2. ウェブ クライアントに名前を付け、[URL] / [オリジン] フィールドは空欄のままにして、[作成] をクリックします。ウェブ クライアントの詳細
  3. 確認ダイアログで、生成されたクライアント ID をコピーします。これは Kotlin コードで使用します。クライアント ID のコピー

4. Android Virtual Device をセットアップする

アプリをテストするには、物理的な Android デバイスまたは Android Virtual Device(AVD)を使用できます。

AVD を作成して実行する

  1. Android Studio でデバイス マネージャーを開き、[Create Virtual Device](または + アイコン)をクリックして、[Medium Phone] を選択します。
  2. システム イメージとして最新の安定版を選択し、[完了] をクリックします。
  3. デバイスの横にある再生/実行アイコンをクリックして、エミュレータを起動します。実行中のデバイス

デバイスで Google アカウントにログインする

  1. エミュレータで、[設定] アプリを開き、[Google] に移動します。
  2. [Google アカウントにログイン] をクリックし、画面の指示に沿って操作します。デバイスにログインしました

5. 依存関係を追加する

認証と Google ID の統合に必要なライブラリをプロジェクトに追加します。

  1. [File] > [Project Structure] > [Dependencies] > [app] に移動します。
  2. [+ > ライブラリの依存関係] をクリックし、com.google.android.libraries.identity.googleid:googleid を検索して、最新バージョン(1.1.1 など)を選択します。
  3. もう一度 [+ > Library Dependency] をクリックし、play-services-auth を検索して、グループ ID が com.google.android.gms のライブラリを選択します。
  4. [OK] をクリックして変更を適用し、プロジェクトを同期します。完了した依存関係

6. ボトムシートのフローを実装する

ボトムシートのフロー

ボトムシート フローは、Credential Manager API を活用して、ユーザーが Android で Google アカウントを使用してアプリにログインするための効率的な方法を提供します。このフローは、特にリピーター ユーザー向けに、スピードと利便性を重視して設計されており、アプリの起動時にトリガーされる必要があります。

ログイン リクエストを作成する

  1. まず、MainActivity.kt を開き、デフォルトの Greeting() 関数と GreetingPreview() 関数を削除します。
  2. 3 行目から始まる既存の import ステートメントの後に、次の import ステートメントを追加します。
    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.kt ファイルの MainActivity クラスの下に、次のコンポーズ可能な関数を追加します。
     @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): コンポーザブルが最初に表示されたときに、すぐにログイン フローをトリガーします。
  • GetGoogleIdOption.Builder(): Google ID トークン リクエストを構成します。
    • setFilterByAuthorizedAccounts(true): まず、ユーザーがこのアプリに対してすでに承認しているアカウントをフィルタして、サイレント ログインを試みます。これにより、リピーターのユーザーの負担を最小限に抑えます。
    • setNonce(...): generateSecureRandomNonce() によってリクエストごとに生成された安全なランダム ノンスを渡して、リプレイ攻撃を防ぎます。
  • signIn(request, context): リクエストを実行します。NoCredentialException で失敗した場合(以前に承認されたアカウントが存在しないことを意味します)、フローは setFilterByAuthorizedAccounts(false) にフォールバックし、ユーザーがデバイスにログインしている Google アカウントから選択できるようにします。

ログイン リクエストを行う

ログイン リクエストが作成されたら、認証情報マネージャーを使用してログイン プロセスを完了できます。リクエストを実行し、発生する可能性のある一般的な例外を処理する signIn という関数を作成します。

MainActivity.kt ファイルの BottomSheet 関数の下に次の関数を追加します。

@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(...): Credential Manager API を呼び出して、システム アカウント セレクタのボトムシートまたはダイアログを表示します。
  • delay(250): 認証情報マネージャー サービスの初期化が完了する前に、アプリの起動時にボトムシートがすぐにトリガーされた場合に、競合状態を防ぐために一時停止します。
  • 例外処理: 一般的な認証情報の誤り(キャンセル、認証情報の欠落、トークンの解析に関する問題など)をキャッチしてログに記録し、トーストを使用してユーザーにフィードバックを提供します。

ボトムシート フローをトリガーする

起動時に BottomSheet() を呼び出すように MainActivity クラスを更新します。YOUR_CLIENT_ID_HERE は、ウェブ アプリケーションのクライアント ID に置き換えます。

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

プロジェクトを保存([File] > [Save])して、アプリケーションを実行します。

  1. 実行ボタン プロジェクトを実行する を押します。
  2. エミュレータでアプリを起動すると、ログイン ボトムシートが表示されます。[続行] をクリックしてフローをテストします。ボトムシート
  3. ログインが成功したことを確認するトースト通知が表示されます。ボトムシートの成功

7. ボタンフローを実装する

ボタンフローの GIF

ボタンフローでは、ユーザーがログインまたは登録するための明示的なオプションが提供されます。標準のブランディングを使用すると、一貫性のあるエクスペリエンスが実現します。「Google でログイン」のブランドの取り扱いガイドラインに準拠した事前承認済みのアセットを使用します。

ブランド アイコンを追加する

  1. ブランド アセットをこちらからダウンロードし、ZIP ファイルを解凍します。
  2. signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png をコピーします。
  3. Android Studio で、ファイルを [res] > [drawable] フォルダに貼り付け、siwg_button.png に名前を変更して、[OK] をクリックします。ボタンを追加する

ボタンフローのコード

このフローは同じ signIn ヘルパー関数を再利用しますが、GetGoogleIdOption ではなく GetSignInWithGoogleOption を渡します。ボトムシート フローとは異なり、明示的なボタンフローでは、保存された認証情報やパスキーが事前にフィルタリングされたり、自動的にプロンプトが表示されたりすることはありません。このコンポーズ可能な関数を 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: ボタンがクリックされたときに、中断 signIn 関数を非同期で実行するコルーチンを起動します。
  • Image: ブランドの siwg_button ドローアブルを表示し、フローをトリガーするクリック リスナーをアタッチします。

ボタンを UI レイアウトに追加する

自動 BottomSheet と明示的な ButtonUI の両方を縦に並べて表示するように、MainActivity レイアウトを更新します。

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. [Sign in with Google](Google でログイン)ボタンをクリックしてログイン ダイアログを開き、アカウントを選択します。ログイン ダイアログ
  4. 結果を確認します。Logcat をチェックして、ユーザー名/メールアドレスの出力が確認できていることを確認します。

8. まとめ

おめでとうございます!Android 認証情報マネージャーを使用して「Google でログイン」を正常に実装しました。

参考情報

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