یاد بگیرید چگونه ورود با گوگل را در برنامه اندروید خود پیاده‌سازی کنید

۱. قبل از شروع

آموزش پیاده سازی ورود با گوگل در اندروید با استفاده از Credential Manager.

پیش‌نیازها

آنچه یاد خواهید گرفت

  • یک پروژه Google Cloud و کلاینت‌های OAuth ایجاد کنید.
  • جریان ورود به سیستم Bottom Sheet را پیاده‌سازی کنید.
  • جریان ورود به سیستم با دکمه را به صورت صریح پیاده‌سازی کنید.

آنچه شما نیاز دارید

۲. یک پروژه اندروید استودیو ایجاد کنید

برای شروع، یک پروژه جدید در اندروید استودیو ایجاد کنید:

  1. اندروید استودیو را باز کنید و روی «پروژه جدید» کلیک کنید. خوش آمدید اندروید استودیو
  2. تلفن و تبلت > خالی کردن فعالیت را انتخاب کنید، سپس روی بعدی کلیک کنید. پروژه اندروید استودیو
  3. تنظیمات پروژه را پیکربندی کنید:
    • نام : یک نام برای پروژه انتخاب کنید.
    • نام بسته : از پیش‌فرض استفاده کنید یا نام دلخواه خود را انتخاب کنید.
    • حداقل SDK : جدیدترین نسخه پایدار یا > اندروید ۱۴ را انتخاب کنید.

پروژه راه اندازی اندروید استودیو

  1. روی Finish کلیک کنید و منتظر بمانید تا ساخت اولیه پروژه تکمیل شود. پروژه اندروید استودیو ساخته شد

۳. پروژه گوگل کلود خود را راه‌اندازی کنید

ایجاد یک پروژه گوگل کلود

  1. به کنسول گوگل کلود بروید و یک پروژه انتخاب یا ایجاد کنید.GCP ایجاد پروژه جدید
  2. به صفحه APIها و خدمات > رضایت OAuth بروید.صفحه رضایت GCP OAuth
  3. روی شروع کلیک کنید و فیلدهای مورد نیاز را پر کنید:
    • نام برنامه : از نام برنامه اندروید خود استفاده کنید.
    • ایمیل پشتیبانی کاربر : حساب گوگل خود را انتخاب کنید.
    • مخاطب : خارجی را انتخاب کنید.
    • اطلاعات تماس : آدرس ایمیل خود را وارد کنید.اطلاعات برنامه GCP
  4. سیاست‌های مربوط به داده‌های کاربر در سرویس‌های API گوگل را مرور کنید و روی ایجاد کلیک کنید.ایجاد GCP

راه‌اندازی کلاینت‌های OAuth

برای دریافت شناسه‌های کلاینت جهت احراز هویت، باید هم یک کلاینت وب و هم یک کلاینت اندروید در کنسول ابری گوگل ایجاد کنید.

  • کلاینت اندروید : با تأیید نام بسته برنامه و امضای SHA-1، درخواست‌ها را ایمن می‌کند.
  • کلاینت وب : به عنوان کلاینت پشتیبان برای سرویس ورود به سیستم گوگل عمل می‌کند.

ایجاد کلاینت اندروید OAuth 2.0

  1. در صفحه کلاینت‌ها ، روی ایجاد کلاینت کلیک کنید و اندروید را به عنوان نوع برنامه انتخاب کنید.GCP ایجاد کلاینت‌ها
  2. نام بسته برنامه خود را وارد کنید (مطابق با خط 1 از MainActivity.kt ).
  3. امضای SHA-1 خود را ایجاد کنید. ترمینال اندروید استودیو را باز کنید و دستور زیر را اجرا کنید: 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 در کنسول جایگذاری کنید و روی ایجاد کلیک کنید. جزئیات کلاینت اندروید

ایجاد کلاینت Web OAuth 2.0

  1. دوباره روی ایجاد کلاینت کلیک کنید و نوع برنامه را Web Application انتخاب کنید.
  2. نام کلاینت وب خود را وارد کنید، فیلدهای URL/Origins را خالی بگذارید و روی Create کلیک کنید.جزئیات کلاینت وب
  3. شناسه کلاینت تولید شده را از پنجره تأیید کپی کنید. از این در کد کاتلین خود استفاده خواهید کرد. کپی کردن شناسه مشتری

۴. یک دستگاه مجازی اندروید راه‌اندازی کنید

برای آزمایش برنامه، می‌توانید از یک دستگاه اندروید فیزیکی یا یک دستگاه مجازی اندروید (AVD) استفاده کنید.

AVD را ایجاد و اجرا کنید

  1. در اندروید استودیو، Device Manager را باز کنید، روی Create Virtual Device (یا آیکون + ) کلیک کنید و Medium Phone را انتخاب کنید.
  2. جدیدترین نسخه پایدار را به عنوان تصویر سیستم انتخاب کنید و روی Finish کلیک کنید.
  3. با کلیک روی آیکون Play/Run در کنار دستگاه، شبیه‌ساز را اجرا کنید. دستگاه در حال اجرا

وارد حساب گوگل خود در دستگاه شوید

  1. در شبیه‌ساز، برنامه تنظیمات (Settings ) را باز کنید و به گوگل (Google) بروید.
  2. روی «ورود به حساب Google خود» کلیک کنید و دستورالعمل‌ها را دنبال کنید. دستگاه وارد شده

۵. وابستگی‌ها را اضافه کنید

کتابخانه‌های مورد نیاز برای احراز هویت و ادغام Google ID را به پروژه خود اضافه کنید:

  1. به مسیر فایل > ساختار پروژه > وابستگی‌ها > برنامه بروید.
  2. روی + > Library Dependency کلیک کنید، عبارت com.google.android.libraries.identity.googleid:googleid را جستجو کنید و آخرین نسخه (مثلاً 1.1.1 ) را انتخاب کنید.
  3. دوباره روی + > Library Dependency کلیک کنید، عبارت play-services-auth را جستجو کنید و کتابخانه‌ای که شناسه گروه آن com.google.android.gms است را انتخاب کنید.
  4. برای اعمال تغییرات و همگام‌سازی پروژه، روی تأیید کلیک کنید. وابستگی‌های تکمیل‌شده

۶. جریان برگه پایانی را پیاده‌سازی کنید

جریان ورق پایین

جریان Bottom Sheet از API مدیریت اعتبارنامه (Credential Manager API) بهره می‌برد تا روشی ساده برای ورود کاربران به برنامه شما با استفاده از حساب‌های گوگل خود در اندروید فراهم کند. این جریان که برای سرعت و راحتی، به ویژه برای کاربران قدیمی طراحی شده است، باید در هنگام اجرای برنامه فعال شود.

درخواست ورود را بسازید

  1. برای شروع، MainActivity.kt را باز کنید و توابع پیش‌فرض Greeting() و GreetingPreview() را حذف کنید.
  2. دستورات 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. این تابع Composable را زیر کلاس 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() : درخواست توکن شناسه گوگل را پیکربندی می‌کند.
    • setFilterByAuthorizedAccounts(true) : ابتدا با فیلتر کردن حساب‌هایی که کاربر قبلاً برای این برنامه مجاز کرده است، ورود بی‌صدا را امتحان می‌کند و دردسر را برای کاربران قدیمی به حداقل می‌رساند.
    • setNonce(...) : یک nonce تصادفی امن که برای هر درخواست توسط generateSecureRandomNonce() تولید می‌شود را برای جلوگیری از حملات بازپخش ارسال می‌کند.
  • signIn(request, context) : درخواست را اجرا می‌کند. اگر با خطای NoCredentialException (به این معنی که هیچ حساب کاربری قبلاً مجاز وجود ندارد) شکست بخورد، جریان به setFilterByAuthorizedAccounts(false) برمی‌گردد تا به کاربر اجازه دهد از هر حساب گوگلی که در دستگاه وارد شده است، انتخاب کند.

درخواست ورود را انجام دهید

با ایجاد درخواست ورود، می‌توانید از Credential Manager برای تکمیل فرآیند ورود استفاده کنید. تابعی به نام 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(...) : رابط برنامه‌نویسی کاربردی مدیریت اعتبار را فراخوانی می‌کند تا برگه یا کادر محاوره‌ای انتخابگر حساب سیستم را نمایش دهد.
  • delay(250) : برای جلوگیری از ایجاد شرایط رقابتی، زمانی که Bottom Sheet بلافاصله در هنگام راه‌اندازی برنامه و قبل از اتمام مقداردهی اولیه سرویس Credential Manager فعال می‌شود، مکث کوتاهی ایجاد می‌کند.
  • مدیریت خطاها : خطاهای رایج اعتبارنامه (مثلاً لغو، فقدان اعتبارنامه یا مشکلات تجزیه توکن) را ثبت و گزارش می‌کند و با استفاده از Toastها به کاربر بازخورد می‌دهد.

جریان برگه پایانی را فعال کنید

کلاس 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)
                }
            }
        }
    }
}

پروژه خود را ذخیره کنید ( File > Save ) و برنامه را اجرا کنید:

  1. دکمه اجرا را فشار دهید:اجرای پروژه
  2. وقتی برنامه روی شبیه‌ساز اجرا می‌شود، صفحه پایانی ورود باید ظاهر شود. برای آزمایش جریان، روی ادامه کلیک کنید.ورق پایین
  3. یک اعلان Toast باید ظاهر شود که تأیید می‌کند ورود به سیستم موفقیت‌آمیز بوده است. موفقیت در برگه نتایج

۷. جریان دکمه را پیاده‌سازی کنید

دکمه جریان GIF

جریان دکمه، گزینه‌ای صریح برای ورود یا ثبت نام کاربران فراهم می‌کند. استفاده از برندسازی استاندارد، تجربه‌ای یکپارچه را تضمین می‌کند. از دارایی‌های از پیش تأیید شده‌ای استفاده کنید که با دستورالعمل‌های برندسازی ورود با گوگل مطابقت دارند.

نماد برند را اضافه کنید

  1. فایل‌های مربوط به برند را از اینجا دانلود کرده و فایل زیپ را استخراج کنید.
  2. signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png را کپی کنید.
  3. در اندروید استودیو، فایل را در پوشه res > drawable خود پیست کنید، نام آن را به siwg_button.png تغییر دهید و روی OK کلیک کنید. دکمه اضافه کردن

کد جریان دکمه

این جریان از همان تابع کمکی signIn استفاده مجدد می‌کند اما به جای GetGoogleIdOption یک GetSignInWithGoogleOption ارسال می‌کند. برخلاف جریان Bottom Sheet، جریان دکمه صریح، اعتبارنامه‌ها یا کلیدهای عبور ذخیره شده را از قبل فیلتر نمی‌کند یا به طور خودکار درخواست نمی‌کند. این تابع Composable را زیر تابع 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 : برخلاف جریان Bottom Sheet، جریان دکمه‌ی صریح از این گزینه برای ترغیب کاربران به انتخاب حساب گوگل خود بدون فیلتر خودکار استفاده می‌کند.
  • 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. برای اجرای پنجره‌ی ورود، روی دکمه‌ی ورود با گوگل کلیک کنید و حساب کاربری خود را انتخاب کنید.گفتگوی ورود
  4. نتیجه را تأیید کنید: برای تأیید چاپ نام کاربری/آدرس ایمیل خود، Logcat را بررسی کنید.

۸. نتیجه‌گیری

تبریک! شما با موفقیت ورود به سیستم با گوگل را با استفاده از 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
    }
}