با نحوه پیاده سازی Sign in With Google در برنامه Android خود آشنا شوید

۱. قبل از شروع

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

پیش‌نیازها

  • درک اولیه از استفاده از کاتلین برای توسعه اندروید
  • درک اولیه از Jetpack Compose (اطلاعات بیشتر را می‌توانید اینجا پیدا کنید)

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

  • نحوه ایجاد یک پروژه ابری گوگل
  • نحوه ایجاد کلاینت‌های OAuth در کنسول ابری گوگل
  • نحوه پیاده‌سازی ورود با گوگل با استفاده از جریان برگه پایانی
  • نحوه پیاده‌سازی ورود با گوگل با استفاده از جریان دکمه

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

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

مدت زمان ۳:۰۰ - ۵:۰۰

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

  1. اندروید استودیو را باز کنید
  2. روی پروژه جدید کلیک کنید خوش آمدید اندروید استودیو
  3. تلفن و تبلت را انتخاب کنید و فعالیت را خالی کنید پروژه اندروید استودیو
  4. روی بعدی کلیک کنید
  5. حالا وقتشه که چند تا از بخش‌های پروژه رو تنظیم کنیم:
    • نام : این نام پروژه شماست
    • نام بسته : این به طور خودکار بر اساس نام پروژه شما پر می‌شود.
    • محل ذخیره : این مکان به طور پیش‌فرض باید در پوشه‌ای باشد که اندروید استودیو پروژه‌های شما را در آن ذخیره می‌کند. می‌توانید این مکان را به هر جایی که دوست دارید تغییر دهید.
    • Minimum SDK : این پایین‌ترین نسخه Android SDK است که برنامه شما برای اجرا روی آن ساخته شده است. در این CodeLab، ما از API 36 (Baklava) استفاده خواهیم کرد.
    پروژه راه اندازی اندروید استودیو
  6. روی پایان کلیک کنید
  7. اندروید استودیو پروژه را ایجاد کرده و هرگونه وابستگی لازم برای برنامه پایه را دانلود می‌کند، این کار می‌تواند چند دقیقه طول بکشد. برای مشاهده این اتفاق، فقط روی نماد ساخت کلیک کنید: ساخت پروژه اندروید استودیو
  8. پس از اتمام این کار، اندروید استودیو باید شبیه به این باشد: پروژه اندروید استودیو ساخته شد

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

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

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

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

حالا که یک پروژه گوگل کلود راه‌اندازی کرده‌ایم، باید یک کلاینت وب و یک کلاینت اندروید اضافه کنیم تا بتوانیم با استفاده از شناسه‌های کلاینت آنها، فراخوانی‌های API را به سرور بک‌اند OAuth انجام دهیم.

برای کلاینت وب اندروید، به موارد زیر نیاز دارید:

  • نام بسته برنامه شما (مثلاً com.example.example)
  • امضای SHA-1 برنامه شما
    • امضای SHA-1 چیست؟
      • اثر انگشت SHA-1 یک هش رمزنگاری است که از کلید امضای برنامه شما تولید می‌شود. این اثر انگشت به عنوان یک شناسه منحصر به فرد برای گواهی امضای برنامه خاص شما عمل می‌کند. آن را مانند یک "امضای" دیجیتال برای برنامه خود در نظر بگیرید.
    • چرا به امضای SHA-1 نیاز داریم؟
      • اثر انگشت SHA-1 تضمین می‌کند که فقط برنامه شما که با کلید امضای خاص شما امضا شده است، می‌تواند با استفاده از شناسه کلاینت OAuth 2.0 شما، توکن‌های دسترسی را درخواست کند و از دسترسی سایر برنامه‌ها (حتی آن‌هایی که نام بسته یکسانی دارند) به منابع پروژه و داده‌های کاربر شما جلوگیری کند.
      • به این شکل فکر کنید:
        • کلید امضای برنامه شما مانند کلید فیزیکی «در» برنامه شماست. این کلید همان چیزی است که امکان دسترسی به عملکرد داخلی برنامه را فراهم می‌کند.
        • اثر انگشت SHA-1 مانند یک شناسه کارت کلید منحصر به فرد است که به کلید فیزیکی شما متصل است. این یک کد خاص است که آن کلید خاص را شناسایی می‌کند.
        • شناسه کلاینت OAuth 2.0 مانند یک کد ورود به یک منبع یا سرویس خاص گوگل (مثلاً Google Sign-in) است.
        • وقتی اثر انگشت SHA-1 را در طول راه‌اندازی کلاینت OAuth ارائه می‌دهید، اساساً به گوگل می‌گویید: «فقط کارت کلید با این شناسه خاص (SHA-1) می‌تواند این کد دسترسی (شناسه کلاینت) را باز کند.» این تضمین می‌کند که فقط برنامه شما می‌تواند به سرویس‌های گوگل مرتبط با آن کد ورودی دسترسی داشته باشد.

برای کلاینت وب، تنها چیزی که نیاز داریم نامی است که می‌خواهید برای شناسایی کلاینت در کنسول استفاده کنید.

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

  1. به صفحه مشتریان برویدمشتریان GCP
  2. روی ایجاد کلاینت کلیک کنیدGCP ایجاد کلاینت‌ها
  3. برای نوع برنامه، اندروید را انتخاب کنید
  4. شما باید نام بسته برنامه خود را مشخص کنید
  5. از اندروید استودیو باید امضای SHA-1 برنامه خود را دریافت کرده و آن را اینجا کپی/پیست کنیم:
    1. به اندروید استودیو بروید و ترمینال را باز کنید.
    2. این دستور را اجرا کنید: مک/لینوکس:
      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
      
      این دستور برای فهرست کردن جزئیات یک ورودی خاص (نام مستعار) در یک keystore طراحی شده است.
      • -list : این گزینه به keytool می‌گوید که محتویات keystore را فهرست کند.
      • -v : این گزینه خروجی شفاف (verbose output) را فعال می‌کند و اطلاعات دقیق‌تری در مورد ورودی ارائه می‌دهد.
      • -keystore ~/.android/debug.keystore : این مسیر فایل keystore را مشخص می‌کند.
      • -alias androiddebugkey : این نام مستعار (نام ورودی) کلیدی را که می‌خواهید بررسی کنید، مشخص می‌کند.
      • -storepass android : این گزینه رمز عبور فایل keystore را ارائه می‌دهد.
      • -keypass android : این گزینه رمز عبور کلید خصوصیِ نام مستعار مشخص شده را ارائه می‌دهد.
    3. مقدار امضای SHA-1 را کپی کنید:
    امضای SHA
    1. به پنجره Google Cloud برگردید و مقدار امضای SHA-1 را وارد کنید:
  6. اکنون صفحه شما باید شبیه به این باشد و می‌توانید روی Create کلیک کنید: جزئیات کلاینت اندرویدکلاینت اندروید

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

  1. برای ایجاد شناسه کلاینت برنامه وب، مراحل ۱-۲ را از بخش ایجاد کلاینت اندروید تکرار کنید و برای نوع برنامه، برنامه وب را انتخاب کنید.
  2. به کلاینت یک نام بدهید (این نام، کلاینت OAuth خواهد بود):جزئیات کلاینت وب
  3. روی ایجاد کلیک کنیدکلاینت وب
  4. ادامه دهید و شناسه کلاینت را از پنجره باز شده کپی کنید، بعداً به آن نیاز خواهید داشتکپی کردن شناسه مشتری

حالا که کلاینت‌های OAuth خود را تنظیم کرده‌ایم، می‌توانیم به اندروید استودیو برگردیم و برنامه‌ی اندروید Sign in With Google خود را بسازیم!

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

برای تست سریع برنامه خود بدون یک دستگاه اندروید فیزیکی، باید یک دستگاه مجازی اندروید ایجاد کنید که بتوانید برنامه خود را از اندروید استودیو بسازید و بلافاصله روی آن اجرا کنید. اگر می‌خواهید با یک دستگاه اندروید فیزیکی تست کنید، می‌توانید دستورالعمل‌های موجود در مستندات توسعه‌دهنده اندروید را دنبال کنید.

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

  1. در اندروید استودیو، Device Manager را باز کنید.مدیر دستگاه
  2. روی دکمه + > ایجاد دستگاه مجازی کلیک کنیدایجاد دستگاه مجازی
  3. از اینجا می‌توانید هر دستگاهی را که برای پروژه خود نیاز دارید اضافه کنید. برای اهداف این Codelab، Medium Phone را انتخاب کنید و سپس روی Next کلیک کنید.تلفن متوسط
  4. حالا می‌توانید دستگاه را برای پروژه خود پیکربندی کنید، برای این کار یک نام منحصر به فرد به آن بدهید، نسخه اندروید مورد استفاده دستگاه را انتخاب کنید و موارد دیگر. مطمئن شوید که API روی API 36 "Baklava" تنظیم شده است؛ اندروید ۱۶، سپس روی Finish کلیک کنید.پیکربندی دستگاه مجازی
  5. شما باید دستگاه جدید را در Device Manager ببینید. برای تأیید اینکه دستگاه در حال اجرا است، ادامه دهید و کلیک کنیددستگاه را اجرا کنید کنار دستگاهی که تازه ایجاد کرده‌ایددستگاه ۲ را اجرا کنید
  6. الان دستگاه باید کار کنه! دستگاه در حال اجرا

وارد دستگاه مجازی اندروید شوید

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

  1. به تنظیمات بروید:
    1. روی مرکز صفحه نمایش در دستگاه مجازی کلیک کنید و به بالا بکشید
    کلیک کنید و بکشید
    1. برنامه تنظیمات (Settings) را پیدا کنید و روی آن کلیک کنید.
    برنامه تنظیمات
  2. در تنظیمات روی گوگل کلیک کنید سرویس‌ها و تنظیمات برگزیده گوگل
  3. روی ورود کلیک کنید و دستورالعمل‌ها را دنبال کنید تا به حساب گوگل خود وارد شوید. ورود به سیستم دستگاه
  1. اکنون باید در دستگاه وارد سیستم شده باشیددستگاه وارد شده

دستگاه مجازی اندروید شما اکنون آماده آزمایش است!

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

مدت زمان ۵:۰۰

برای برقراری تماس‌های API مربوط به OAuth، ابتدا باید کتابخانه‌های مورد نیاز را که به ما امکان می‌دهند درخواست‌های احراز هویت را انجام دهیم، ادغام کنیم و از شناسه‌های گوگل برای انجام این درخواست‌ها استفاده کنیم:

  • شناسه گوگل
  • libs.play.services.auth
  1. به فایل > ساختار پروژه بروید:ساختار پروژه
  2. سپس به وابستگی‌ها > برنامه > '+' > وابستگی کتابخانه برویدوابستگی‌ها
  3. حالا باید کتابخانه‌هایمان را اضافه کنیم:
    1. در کادر جستجو، googleid را تایپ کنید و روی جستجو کلیک کنید.
    2. فقط باید یک ورودی وجود داشته باشد، آن را انتخاب کنید و بالاترین نسخه موجود را انتخاب کنید (در زمان نگارش این Codelab نسخه ۱.۱.۱ است)
    3. روی تأیید کلیک کنیدبسته شناسه گوگل
    4. مراحل ۱ تا ۳ را تکرار کنید، اما به جای آن عبارت "play-services-auth" را جستجو کنید و خطی را که "com.google.android.gms" به عنوان شناسه گروه و "play-services-auth" به عنوان نام مصنوع دارد، انتخاب کنید.تأیید خدمات بازی
  4. روی تأیید کلیک کنید وابستگی‌های تکمیل‌شده

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

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

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

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

  1. برای شروع، توابع Greeting() و GreetingPreview() را از فایل MainActivity.kt حذف کنید، دیگر به آنها نیازی نخواهیم داشت.
  2. حالا باید مطمئن شویم که بسته‌های مورد نیاز برای این پروژه وارد شده‌اند. دستورات import زیر را بعد از دستورات موجود از خط ۳ اضافه کنید:
    import android.content.ContentValues.TAG
    import android.content.Context
    import android.credentials.GetCredentialException
    import android.os.Build
    import android.util.Log
    import android.widget.Toast
    import androidx.annotation.RequiresApi
    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
    
  3. در مرحله بعد، باید تابع خود را برای ساخت درخواست Bottom Sheet ایجاد کنیم. این کد را در زیر کلاس MainActivity قرار دهید.
   //This line is not needed for the project to build, but you will see errors if it is not present.
   //This code will not work on Android versions < UpsideDownCake
   @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)
   }

بیایید ببینیم این کد چه کاری انجام می‌دهد:

fun BottomSheet(webClientId: String) {...} : تابعی به نام BottomSheet ایجاد می‌کند که یک آرگومان رشته‌ای به نام webClientid دریافت می‌کند.

  • val context = LocalContext.current : زمینه فعلی اندروید را بازیابی می‌کند. این برای عملیات مختلف، از جمله راه‌اندازی اجزای رابط کاربری، مورد نیاز است.
  • LaunchedEffect(Unit) { ... } : LaunchedEffect یک Composable از Jetpack Compose است که به شما امکان می‌دهد یک تابع suspend (تابعی که می‌تواند اجرا را متوقف کرده و از سر بگیرد) را در چرخه حیات Composable اجرا کنید. واحد به عنوان کلید به این معنی است که این effect فقط یک بار و در اولین اجرای Composable اجرا می‌شود.
    • val googleIdOption: GetGoogleIdOption = ... : یک شیء GetGoogleIdOption ایجاد می‌کند. این شیء نوع اعتبارنامه‌ای که از گوگل درخواست می‌شود را پیکربندی می‌کند.
      • .Builder() : از الگوی سازنده برای پیکربندی گزینه‌ها استفاده می‌شود.
      • .setFilterByAuthorizedAccounts(true) : مشخص می‌کند که آیا به کاربر اجازه داده شود از بین همه حساب‌های گوگل انتخاب کند یا فقط حساب‌هایی که قبلاً برنامه را مجاز کرده‌اند. در این حالت، روی true تنظیم شده است، به این معنی که درخواست با استفاده از اعتبارنامه‌هایی که کاربر قبلاً برای استفاده با این برنامه مجاز کرده است، در صورت وجود، انجام می‌شود.
      • .setServerClientId(webClientId) : شناسه کلاینت سرور را تنظیم می‌کند، که یک شناسه منحصر به فرد برای backend برنامه شما است. این برای دریافت توکن شناسه لازم است.
      • .setNonce(generateSecureRandomNonce()) : یک nonce، یک مقدار تصادفی، برای جلوگیری از حملات بازپخش و اطمینان از مرتبط بودن توکن شناسه با درخواست خاص، تنظیم می‌کند.
      • .build() : شیء GetGoogleIdOption را با پیکربندی مشخص شده ایجاد می‌کند.
    • val request: GetCredentialRequest = ... : یک شیء GetCredentialRequest ایجاد می‌کند. این شیء کل درخواست اعتبارنامه را کپسوله‌سازی می‌کند.
      • .Builder() : الگوی سازنده را برای پیکربندی درخواست آغاز می‌کند.
      • .addCredentialOption(googleIdOption) : googleIdOption را به درخواست اضافه می‌کند و مشخص می‌کند که ما می‌خواهیم یک توکن شناسه گوگل درخواست کنیم.
      • .build() : شیء GetCredentialRequest را ایجاد می‌کند.
    • val e = signIn(request, context) : این تابع تلاش می‌کند تا کاربر را با درخواست ایجاد شده و زمینه فعلی وارد سیستم کند. نتیجه تابع signIn در e ذخیره می‌شود. این متغیر یا حاوی نتیجه موفقیت‌آمیز یا حاوی یک استثنا خواهد بود.
    • if (e is NoCredentialException) { ... } : این یک بررسی شرطی است. اگر تابع signIn با خطای NoCredentialException مواجه شود، به این معنی است که هیچ حساب کاربری قبلاً مجاز شده‌ای در دسترس نیست.
      • val googleIdOptionFalse: GetGoogleIdOption = ... : اگر signIn قبلی ناموفق بود، این بخش یک GetGoogleIdOption جدید ایجاد می‌کند.
      • .setFilterByAuthorizedAccounts(false) : این تفاوت اساسی با گزینه اول است. این گزینه فیلتر کردن حساب‌های مجاز را غیرفعال می‌کند، به این معنی که هر حساب گوگلی روی دستگاه می‌تواند برای ورود به سیستم استفاده شود.
      • val requestFalse: GetCredentialRequest = ... : یک GetCredentialRequest جدید با googleIdOptionFalse ایجاد می‌شود.
      • signIn(requestFalse, context) : این متد تلاش می‌کند تا کاربر را با درخواست جدیدی که اجازه استفاده از هر حسابی را می‌دهد، وارد سیستم کند.

در اصل، این کد با استفاده از پیکربندی‌های ارائه شده، درخواستی را به API مدیریت اعتبارنامه (Credential Manager API) ارسال می‌کند تا یک توکن شناسه گوگل (Google ID token) برای کاربر بازیابی کند. سپس می‌توان از GetCredentialRequest برای راه‌اندازی رابط کاربری مدیریت اعتبارنامه استفاده کرد، جایی که کاربر می‌تواند حساب گوگل خود را انتخاب کرده و مجوزهای لازم را اعطا کند.

fun generateSecureRandomNonce(byteLength: Int = 32): String : این تابعی به نام generateSecureRandomNonce تعریف می‌کند. این تابع یک آرگومان عدد صحیح byteLength (با مقدار پیش‌فرض ۳۲) را می‌پذیرد که طول مطلوب nonce را بر حسب بایت مشخص می‌کند. این تابع یک رشته برمی‌گرداند که نمایش کدگذاری شده Base64 از بایت‌های تصادفی خواهد بود.

  • val randomBytes = ByteArray(byteLength) : یک آرایه بایتی از byteLength مشخص شده برای نگهداری بایت‌های تصادفی ایجاد می‌کند.
  • SecureRandom.getInstanceStrong().nextBytes(randomBytes) :
    • SecureRandom.getInstanceStrong() : این یک مولد اعداد تصادفی قوی از نظر رمزنگاری به دست می‌آورد. این برای امنیت بسیار مهم است، زیرا تضمین می‌کند که اعداد تولید شده واقعاً تصادفی و غیرقابل پیش‌بینی هستند. این تابع از قوی‌ترین منبع آنتروپی موجود در سیستم استفاده می‌کند.
    • .nextBytes(randomBytes) : این تابع آرایه randomBytes را با بایت‌های تصادفی تولید شده توسط نمونه SecureRandom پر می‌کند.
  • return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
    • Base64.getUrlEncoder() : این یک رمزگذار Base64 دریافت می‌کند که از الفبای URL-safe استفاده می‌کند (استفاده از - و _ به جای + و /). این مهم است زیرا تضمین می‌کند که رشته حاصل می‌تواند بدون نیاز به رمزگذاری بیشتر، با خیال راحت در URLها استفاده شود.
    • .withoutPadding() : این تابع هرگونه کاراکتر فاصله‌گذاری (padding) را از رشته کدگذاری شده Base64 حذف می‌کند. این اغلب برای کوتاه‌تر و فشرده‌تر کردن nonce مطلوب است.
    • .encodeToString(randomBytes) : این تابع randomBytes را در یک رشته Base64 کدگذاری کرده و آن را برمی‌گرداند.

به طور خلاصه، این تابع یک نانس تصادفی قوی از نظر رمزنگاری با طول مشخص تولید می‌کند، آن را با استفاده از Base64 که برای URL ایمن است، کدگذاری می‌کند و رشته حاصل را برمی‌گرداند. این یک روش استاندارد برای تولید نانس‌هایی است که برای استفاده در زمینه‌های حساس به امنیت ایمن هستند.

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

اکنون که می‌توانیم درخواست ورود به سیستم خود را بسازیم، می‌توانیم از Credential Manager برای ورود به سیستم استفاده کنیم. برای انجام این کار، باید تابعی ایجاد کنیم که درخواست‌های ورود به سیستم را با استفاده از Credential Manager مدیریت کند، ضمن اینکه استثنائات رایجی را که ممکن است با آنها مواجه شویم، مدیریت کند.

برای انجام این کار، می‌توانید این تابع را زیر تابع BottomSheet() قرار دهید.

//This code will not work on Android versions < UPSIDE_DOWN_CAKE when GetCredentialException is
//is thrown.
@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!"
    var e: Exception? = null
    //using delay() here helps prevent NoCredentialException when the BottomSheet Flow is triggered
    //on the initial running of our app
    delay(250)
    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())

        Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
        Log.i(TAG, "(☞゚ヮ゚)☞  Sign in Successful!  ☜(゚ヮ゚☜)")

    } catch (e: GetCredentialException) {
        Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
        Log.e(TAG, failureMessage + ": Failure getting credentials", e)

    } catch (e: GoogleIdTokenParsingException) {
        Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
        Log.e(TAG, failureMessage + ": Issue with parsing received GoogleIdToken", e)

    } catch (e: NoCredentialException) {
        Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
        Log.e(TAG, failureMessage + ": No credentials found", e)
        return e

    } catch (e: GetCredentialCustomException) {
        Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
        Log.e(TAG, failureMessage + ": Issue with custom credential request", e)

    } catch (e: GetCredentialCancellationException) {
        Toast.makeText(context, ": Sign-in cancelled", Toast.LENGTH_SHORT).show()
        Log.e(TAG, failureMessage + ": Sign-in was cancelled", e)
    }
    return e
}

حالا به بررسی عملکرد کد در اینجا می‌پردازیم:

suspend fun signIn(request: GetCredentialRequest, context: Context): Exception? : این یک تابع suspend به نام signIn تعریف می‌کند. این بدان معناست که می‌تواند بدون مسدود کردن thread اصلی، متوقف و از سر گرفته شود. این تابع یک Exception? برمی‌گرداند که در صورت موفقیت‌آمیز بودن ورود، null و در صورت ناموفق بودن ورود، همان exception خاص را برمی‌گرداند.

دو پارامتر می‌گیرد:

  • request : یک شیء GetCredentialRequest که شامل پیکربندی نوع اعتبارنامه‌ای است که باید بازیابی شود (مثلاً شناسه گوگل).
  • context : همان Context اندروید است که برای تعامل با سیستم مورد نیاز است.

برای بدنه تابع:

  • val credentialManager = CredentialManager.create(context) : یک نمونه از CredentialManager ایجاد می‌کند که رابط اصلی برای تعامل با API مربوط به Credential Manager است. برنامه به این ترتیب جریان ورود را آغاز خواهد کرد.
  • val failureMessage = "Sign in failed!" : یک رشته (failureMessage) تعریف می‌کند که در صورت عدم موفقیت ورود به سیستم، در یک پیام Toast نمایش داده می‌شود.
  • var e: Exception? = null : این خط متغیر e را برای ذخیره هر استثنایی که ممکن است در طول فرآیند رخ دهد، با شروع از null، مقداردهی اولیه می‌کند.
  • delay(250) : یک تأخیر ۲۵۰ میلی‌ثانیه‌ای ایجاد می‌کند. این یک راه حل برای یک مشکل بالقوه است که در آن ممکن است NoCredentialException بلافاصله هنگام شروع برنامه، به خصوص هنگام استفاده از جریان BottomSheet، رخ دهد. این به سیستم زمان می‌دهد تا مدیریت اعتبارنامه را مقداردهی اولیه کند.
  • try { ... } catch (e: Exception) { ... } : یک بلوک try-catch برای مدیریت قوی خطا استفاده می‌شود. این تضمین می‌کند که اگر در طول فرآیند ورود به سیستم خطایی رخ دهد، برنامه از کار نمی‌افتد و می‌تواند خطا را به خوبی مدیریت کند.
    • val result = credentialManager.getCredential(request = request, context = context) : اینجا جایی است که فراخوانی واقعی به API مدیریت اعتبارنامه اتفاق می‌افتد و فرآیند بازیابی اعتبارنامه را آغاز می‌کند. این API درخواست و زمینه را به عنوان ورودی دریافت می‌کند و یک رابط کاربری (UI) برای انتخاب اعتبارنامه به کاربر ارائه می‌دهد. در صورت موفقیت، نتیجه‌ای حاوی اعتبارنامه انتخاب شده را برمی‌گرداند. نتیجه این عملیات، GetCredentialResponse ، در متغیر result ذخیره می‌شود.
    • Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show() : یک پیام کوتاه مبنی بر موفقیت‌آمیز بودن ورود به سیستم نمایش می‌دهد.
    • Log.i(TAG, "Sign in Successful!") : یک پیام جالب و موفقیت‌آمیز را در logcat ثبت می‌کند.
    • catch (e: GetCredentialException) : استثنائات از نوع GetCredentialException را مدیریت می‌کند. این یک کلاس والد برای چندین استثنای خاص است که می‌توانند در طول فرآیند واکشی اعتبارنامه رخ دهند.
    • catch (e: GoogleIdTokenParsingException) : استثنائاتی را که هنگام خطایی در تجزیه توکن شناسه گوگل رخ می‌دهند، مدیریت می‌کند.
    • catch (e: NoCredentialException) : خطای NoCredentialException را مدیریت می‌کند، که زمانی رخ می‌دهد که هیچ اعتبارنامه‌ای برای کاربر در دسترس نباشد (مثلاً هیچ اعتباری ذخیره نکرده باشد، یا حساب گوگل نداشته باشد).
      • نکته‌ی مهم این است که این تابع، استثنای ذخیره شده در e ، NoCredentialException ، را برمی‌گرداند و به فراخواننده اجازه می‌دهد در صورت عدم وجود اعتبارنامه، مورد خاص را مدیریت کند.
    • catch (e: GetCredentialCustomException) : استثنائات سفارشی را که ممکن است توسط ارائه دهنده اعتبارنامه ایجاد شوند، مدیریت می‌کند.
    • catch (e: GetCredentialCancellationException) : استثنای GetCredentialCancellationException را مدیریت می‌کند، که زمانی که کاربر فرآیند ورود به سیستم را لغو می‌کند، رخ می‌دهد.
    • Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show() : با استفاده از failMessage یک پیام toast نمایش می‌دهد که نشان می‌دهد ورود به سیستم ناموفق بوده است.
    • Log.e(TAG, "", e) : با استفاده از Log.e، استثنا را در logcat اندروید ثبت می‌کند که برای خطاها استفاده می‌شود. این شامل stacktrace استثنا برای کمک به اشکال‌زدایی است. همچنین شامل شکلک عصبانی برای سرگرمی است.
  • return e : این تابع در صورت بروز هرگونه خطا، آن را برمی‌گرداند و در صورت موفقیت‌آمیز بودن ورود، مقدار null را برمی‌گرداند.

به طور خلاصه، این کد روشی برای مدیریت ورود کاربر با استفاده از رابط برنامه‌نویسی مدیریت اعتبارنامه (Credential Manager API) ارائه می‌دهد، عملیات ناهمزمان را مدیریت می‌کند، خطاهای احتمالی را مدیریت می‌کند و از طریق Toastها و Logها به کاربر بازخورد می‌دهد و در عین حال کمی چاشنی طنز به مدیریت خطا اضافه می‌کند.

جریان برگه پایانی را روی برنامه پیاده‌سازی کنید

اکنون می‌توانیم با استفاده از کد زیر و شناسه کلاینت برنامه وب خود که قبلاً از کنسول Google Cloud کپی کرده‌ایم، فراخوانی برای فعال کردن جریان BottomSheet در کلاس 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,
                ) {
                    //This will trigger on launch
                    BottomSheet(webClientId)
                }
            }
        }
    }
}

حالا می‌توانیم پروژه‌مان را ذخیره کنیم ( File > Save ) و آن را اجرا کنیم:

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

۷. جریان دکمه

دکمه جریان GIF

دکمه‌ی ورود با گوگل، ثبت نام یا ورود به برنامه‌ی اندروید شما را با استفاده از حساب گوگل فعلی کاربران آسان‌تر می‌کند. اگر کاربران از صفحه‌ی اصلی صرف نظر کنند یا صرفاً ترجیح دهند از حساب گوگل خود برای ورود یا ثبت نام استفاده کنند، با این گزینه مواجه خواهند شد. برای توسعه‌دهندگان، این به معنای آشنایی روان‌تر و اصطکاک کمتر در حین ثبت نام است.

اگرچه این کار را می‌توان با دکمه‌ی آماده‌ی Jetpack Compose انجام داد، ما از یک نماد برند از پیش تأیید شده از صفحه‌ی «ورود با دستورالعمل‌های برندسازی گوگل» استفاده خواهیم کرد.

اضافه کردن آیکون برند به پروژه

  1. فایل زیپ آیکون‌های برند از پیش تایید شده را از اینجا دانلود کنید
  2. فایل signin-assest.zip را از دانلودهایتان از حالت فشرده خارج کنید (این فایل بسته به سیستم عامل رایانه شما متفاوت خواهد بود). اکنون می‌توانید پوشه signin-assets را باز کنید و آیکون‌های موجود را بررسی کنید. برای این Codelab، ما از signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png استفاده خواهیم کرد.
  3. فایل را کپی کنید
  4. با کلیک راست روی پوشه drawable و انتخاب گزینه Paste ، آن را در پروژه اندروید استودیو و در مسیر res > drawable قرار دهید (ممکن است برای مشاهده آن مجبور شوید پوشه res را باز کنید).قابل ترسیم
  5. یک کادر محاوره‌ای نمایش داده می‌شود که از شما می‌خواهد نام فایل را تغییر دهید و دایرکتوری که قرار است به آن اضافه شود را تأیید کنید. نام فایل را به siwg_button.png تغییر دهید و سپس روی تأیید کلیک کنید. دکمه اضافه کردن

کد جریان دکمه

این کد از همان تابع signIn() که برای BottomSheet() استفاده می‌شود، استفاده می‌کند، اما به جای 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)
    )
}

برای تجزیه و تحلیل عملکرد کد:

fun ButtonUI(webClientId: String) : این تابعی به نام ButtonUI تعریف می‌کند که یک webClientId (شناسه کلاینت پروژه Google Cloud شما) را به عنوان آرگومان می‌پذیرد.

val context = LocalContext.current : زمینه فعلی اندروید را بازیابی می‌کند. این برای عملیات مختلف، از جمله راه‌اندازی اجزای رابط کاربری، مورد نیاز است.

val coroutineScope = rememberCoroutineScope() : یک محدوده کوروتین ایجاد می‌کند. این برای مدیریت وظایف ناهمزمان استفاده می‌شود و به کد اجازه می‌دهد بدون مسدود کردن نخ اصلی اجرا شود. rememberCoroutineScope () یک تابع composable از Jetpack Compose است که محدوده‌ای را فراهم می‌کند که به چرخه حیات composable گره خورده است.

val onClick: () -> Unit = { ... } : این یک تابع لامبدا ایجاد می‌کند که هنگام کلیک روی دکمه اجرا می‌شود. تابع لامبدا:

  • val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(serverClientId = webClientId).setNonce(generateSecureRandomNonce()).build() : این بخش یک شیء GetSignInWithGoogleOption ایجاد می‌کند. این شیء برای تعیین پارامترهای فرآیند "ورود با گوگل" استفاده می‌شود و به webClientId و یک nonce (یک رشته تصادفی که برای امنیت استفاده می‌شود) نیاز دارد.
  • val request: GetCredentialRequest = GetCredentialRequest.Builder().addCredentialOption(signInWithGoogleOption).build() : این یک شیء GetCredentialRequest می‌سازد. این درخواست برای دریافت اعتبارنامه کاربر با استفاده از Credential Manager استفاده خواهد شد. GetCredentialRequest GetSignInWithGoogleOption که قبلاً ایجاد شده بود را به عنوان یک گزینه اضافه می‌کند تا اعتبارنامه "ورود با گوگل" را درخواست کند.
  • coroutineScope.launch { ... } : یک CoroutineScope برای مدیریت عملیات ناهمزمان (با استفاده از Coroutine).
    • signIn(request, context) : تابع signIn () که قبلاً تعریف شده است را فراخوانی می‌کند.

Image(...) : این تابع با استفاده از painterResource که تصویر R.drawable.siwg_button را بارگذاری می‌کند، یک تصویر را رندر می‌کند.

  • Modifier.fillMaxSize().clickable(enabled = true, onClick = onClick) :
    • fillMaxSize() باعث می‌شود تصویر تمام فضای موجود را پر کند.
    • clickable(enabled = true, onClick = onClick) : تصویر را قابل کلیک می‌کند و وقتی روی آن کلیک می‌شود، تابع لامبدا onClick که قبلاً تعریف شده است را اجرا می‌کند.

به طور خلاصه، این کد یک دکمه «ورود با گوگل» را در رابط کاربری Jetpack Compose تنظیم می‌کند. وقتی روی دکمه کلیک می‌شود، یک درخواست اعتبارنامه برای راه‌اندازی Credential Manager آماده می‌کند و به کاربر اجازه می‌دهد با حساب گوگل خود وارد شود.

حالا باید کلاس MainActivity را برای اجرای تابع 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)
                    }
                }
            }
        }
    }
}

حالا می‌توانیم پروژه‌مان را ذخیره کنیم ( File > Save ) و آن را اجرا کنیم:

  1. دکمه اجرا را فشار دهید:اجرای پروژه
  2. وقتی برنامه‌ها روی شبیه‌ساز اجرا شدند، BottomSheet باید نمایش داده شود. برای بستن آن، روی قسمت بیرونی آن کلیک کنید.اینجا را لمس کنید
  3. حالا باید دکمه‌ای که ایجاد کردیم را در برنامه ببینید. روی آن کلیک کنید تا کادر محاوره‌ای ورود به سیستم را ببینید.ورود به سیستم گفتگو
  4. برای ورود به حساب کاربری خود کلیک کنید!

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

شما این آزمایشگاه کدنویسی را تمام کردید! برای اطلاعات بیشتر یا کمک در مورد ورود با گوگل در اندروید، به بخش سوالات متداول در زیر مراجعه کنید:

سوالات متداول

کد کامل 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.credentials.GetCredentialException
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
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)
                    }
                }
            }
        }
    }
}

//This line is not needed for the project to build, but you will see errors if it is not present.
//This code will not work on Android versions < UpsideDownCake
@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()

        signIn(coroutineScope, request, context)
    }
    Image(
        painter = painterResource(id = R.drawable.siwg_button),
        contentDescription = "",
        modifier = Modifier
            .fillMaxSize()
            .clickable(enabled = true, onClick = onClick)
    )
}

fun generateSecureRandomNonce(byteLength: Int = 32): String {
    val randomBytes = ByteArray(byteLength)
    SecureRandom.getInstanceStrong().nextBytes(randomBytes)
    return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
}

//This code will not work on Android versions < UPSIDE_DOWN_CAKE when GetCredentialException is
//is thrown.
@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!"
    var e: Exception? = null
    //using delay() here helps prevent NoCredentialException when the BottomSheet Flow is triggered
    //on the initial running of our app
    delay(250)
    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())

        Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
        Log.i(TAG, "(☞゚ヮ゚)☞  Sign in Successful!  ☜(゚ヮ゚☜)")

    } catch (e: GetCredentialException) {
        Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
        Log.e(TAG, failureMessage + ": Failure getting credentials", e)

    } catch (e: GoogleIdTokenParsingException) {
        Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
        Log.e(TAG, failureMessage + ": Issue with parsing received GoogleIdToken", e)

    } catch (e: NoCredentialException) {
        Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
        Log.e(TAG, failureMessage + ": No credentials found", e)
        return e

    } catch (e: GetCredentialCustomException) {
        Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
        Log.e(TAG, failureMessage + ": Issue with custom credential request", e)

    } catch (e: GetCredentialCancellationException) {
        Toast.makeText(context, ": Sign-in cancelled", Toast.LENGTH_SHORT).show()
        Log.e(TAG, failureMessage + ": Sign-in was cancelled", e)
    }
    return e
}