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

1. قبل از شروع

در این کد لبه یاد می گیرید که چگونه با استفاده از Credential Manager در اندروید Sign in with Google را پیاده سازی کنید.

پیش نیازها

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

چیزی که یاد خواهید گرفت

  • چگونه یک پروژه Google Cloud ایجاد کنیم
  • نحوه ایجاد مشتریان OAuth در Google Cloud Console
  • نحوه اجرای Sign in With Google با استفاده از جریان پایین صفحه
  • نحوه اجرای Sign in With Google با استفاده از جریان دکمه

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

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

مدت زمان 3:00 - 5:00

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

  1. اندروید استودیو را باز کنید
  2. روی New Project کلیک کنیدAndroid Studio خوش آمدید
  3. Phone and Tablet and Empty Activity را انتخاب کنید پروژه اندروید استودیو
  4. روی Next کلیک کنید
  5. اکنون زمان تنظیم چند قطعه از پروژه است:
    • نام : این نام پروژه شماست
    • نام بسته : این بسته به صورت خودکار بر اساس نام پروژه شما پر می شود
    • ذخیره مکان : این باید به طور پیش فرض پوشه ای باشد که Android Studio پروژه های شما را در آن ذخیره می کند. می توانید آن را به هر جایی که می خواهید تغییر دهید.
    • حداقل SDK : این پایین ترین نسخه Android SDK است که برنامه شما برای اجرا ساخته شده است. در این CodeLab از API 36 (باقلوا) استفاده خواهیم کرد.
    پروژه راه اندازی اندروید استودیو
  6. روی Finish کلیک کنید
  7. Android Studio پروژه را ایجاد می کند و هر وابستگی لازم را برای برنامه پایه دانلود می کند، این می تواند چند دقیقه طول بکشد. برای مشاهده این اتفاق، فقط روی نماد ساخت کلیک کنید: ساختمان پروژه اندروید استودیو
  8. پس از تکمیل، Android Studio باید شبیه به این باشد: پروژه اندروید استودیو ساخته شده است

3. پروژه Google Cloud خود را راه اندازی کنید

یک پروژه Google Cloud ایجاد کنید

  1. به Google Cloud Console بروید
  2. پروژه خود را باز کنید یا یک پروژه جدید ایجاد کنید GCP پروژه جدید ایجاد می کندGCP پروژه جدید 2 ایجاد می کندGCP پروژه جدید 3 ایجاد می کند
  3. روی APIs & Services کلیک کنیدAPIها و خدمات GCP
  4. به صفحه رضایت OAuth برویدصفحه رضایت OAuth GCP
  5. برای ادامه، باید فیلدهای نمای کلی را پر کنید. برای شروع پر کردن این اطلاعات روی Get Start کلیک کنید:دکمه شروع GCP
    • نام برنامه : نام این برنامه، که باید همان چیزی باشد که هنگام ایجاد پروژه در Android Studio استفاده کرده اید
    • ایمیل پشتیبانی کاربر : این نشان می دهد که حساب Google شما با آن وارد سیستم شده اید و هر گروه Google که مدیریت می کنید
    اطلاعات برنامه GCP
    • مخاطب :
      • داخلی برای برنامه‌ای که فقط در سازمان شما استفاده می‌شود. اگر سازمانی مرتبط با Google Cloud Project ندارید، نمی‌توانید این را انتخاب کنید.
      • خارجی همان چیزی است که ما استفاده می کنیم.
    مخاطب GCP
    • اطلاعات تماس : این می تواند هر ایمیلی را داشته باشد که می خواهید نقطه تماس برنامه باشد
    اطلاعات تماس GCP
    • خدمات Google API: سیاست داده کاربر را مرور کنید.
  6. پس از بررسی و موافقت با خط مشی داده های کاربر ، روی ایجاد کلیک کنیدایجاد GCP

مشتریان OAuth را تنظیم کنید

اکنون که یک Google Cloud Project راه‌اندازی کرده‌ایم، باید یک Web Client و Android Client اضافه کنیم تا بتوانیم با شناسه‌های کلاینت آنها با سرور Backend OAuth تماس‌های API برقرار کنیم.

برای Android Web Client، شما نیاز دارید:

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

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

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

  1. به صفحه مشتریان برویدمشتریان GCP
  2. روی Create Client کلیک کنیدGCP ایجاد مشتریان
  3. اندروید را برای نوع Application انتخاب کنید
  4. شما باید نام بسته برنامه خود را مشخص کنید
  5. از Android Studio باید امضای SHA-1 برنامه خود را دریافت کرده و آن را در اینجا کپی/پیست کنید:
    1. به اندروید استودیو بروید و ترمینال را باز کنید
    2. این دستور را اجرا کنید:
      keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
      
      این دستور برای فهرست کردن جزئیات یک ورودی خاص (نام مستعار) در یک فروشگاه کلید طراحی شده است.
      • -list : این گزینه به keytool می‌گوید محتویات keystore را فهرست کند.
      • -v : این گزینه خروجی مفصل را فعال می کند و اطلاعات دقیق تری در مورد ورودی ارائه می دهد.
      • -keystore ~/.android/debug.keystore : مسیر فایل keystore را مشخص می کند.
      • -alias androiddebugkey : نام مستعار (نام ورودی) کلیدی را که می خواهید بررسی کنید مشخص می کند.
      • -storepass android : رمز عبور فایل keystore را ارائه می دهد.
      • -keypass android : رمز عبور کلید خصوصی نام مستعار مشخص شده را ارائه می دهد.
    3. مقدار امضای SHA-1 را کپی کنید:
    SHA امضاء
    1. به پنجره Google Cloud برگردید و مقدار امضای SHA-1 را جای‌گذاری کنید:
  6. اکنون صفحه شما باید شبیه به این باشد و می توانید روی ایجاد کلیک کنید: جزئیات کلاینت اندرویدکلاینت اندروید

سرویس گیرنده وب OAuth 2.0 ایجاد کنید

  1. برای ایجاد شناسه سرویس گیرنده برنامه وب، مراحل 1-2 را از قسمت Create Android Client تکرار کنید و Web Application را برای نوع برنامه انتخاب کنید.
  2. به مشتری یک نام بدهید (این مشتری OAuth خواهد بود):جزئیات مشتری وب
  3. روی ایجاد کلیک کنیدمشتری وب
  4. پیش بروید و شناسه مشتری را از پنجره بازشو کپی کنید، بعداً به آن نیاز خواهید داشت شناسه مشتری را کپی کنید

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

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

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

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

  1. در Android Studio، Device Manager را باز کنیدمدیر دستگاه
  2. روی دکمه + > Create Virtual Device کلیک کنید یک دستگاه مجازی ایجاد کنید
  3. از اینجا می توانید هر دستگاهی را که برای پروژه خود نیاز دارید اضافه کنید. برای اهداف این Codelab، تلفن متوسط را انتخاب کنید سپس روی Next کلیک کنیدتلفن متوسط
  4. اکنون می‌توانید با دادن یک نام منحصر به فرد، انتخاب نسخه اندرویدی که دستگاه اجرا می‌شود و موارد دیگر، دستگاه را برای پروژه خود پیکربندی کنید. اطمینان حاصل کنید که API روی API 36 "باقلوا" تنظیم شده است. اندروید 16 سپس روی Finish کلیک کنیدپیکربندی دستگاه مجازی
  5. باید ببینید دستگاه جدید در Device Manager نمایش داده می شود. برای تأیید اینکه دستگاه اجرا می شود، ادامه دهید و کلیک کنیددستگاه را اجرا کنید در کنار دستگاهی که به تازگی ایجاد کرده ایددستگاه 2 را اجرا کنید
  6. دستگاه باید اکنون در حال اجرا باشد! دستگاه در حال اجرا

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

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

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

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

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

مدت زمان 5:00

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

  • libs.googleid
  • libs.play.services.auth
  1. به File > Project Structure بروید:ساختار پروژه
  2. سپس به Dependencies > app > '+' > Library Dependency برویدوابستگی ها
  3. اکنون باید کتابخانه های خود را اضافه کنیم:
    1. در گفتگوی جستجو، googleid را تایپ کنید و روی جستجو کلیک کنید
    2. فقط باید یک ورودی وجود داشته باشد، ادامه دهید و آن را انتخاب کنید و بالاترین نسخه موجود را انتخاب کنید (در زمان این Codelab itis 1.1.1)
    3. روی OK کلیک کنیدبسته Google ID
    4. مراحل 1-3 را تکرار کنید اما به جای آن عبارت "play-services-auth" را جستجو کنید و خط را با "com.google.android.gms" به عنوان شناسه گروه و "play-services-auth" به عنوان نام Artifact انتخاب کنید.Play Services Auth
  4. روی OK کلیک کنیدوابستگی های تمام شده

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

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

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

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

  1. برای شروع، ادامه دهید و توابع Greeting() و GreetingPreview() را از MainActivity.kt حذف کنید، ما به آنها نیازی نخواهیم داشت.
  2. اکنون باید مطمئن شویم که بسته های مورد نیاز ما برای این پروژه وارد شده است. ادامه دهید و عبارات import زیر را بعد از موارد موجود در خط 3 اضافه کنید:
    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 : زمینه Android فعلی را بازیابی می کند. این برای عملیات های مختلف، از جمله راه اندازی مؤلفه های UI مورد نیاز است.
  • LaunchedEffect(Unit) { ... } : LaunchedEffect یک Jetpack Compose Composable است که به شما امکان می دهد یک تابع تعلیق (عملکردی که می تواند متوقف شود و اجرا را از سر بگیرد) در چرخه حیات composable اجرا کنید. واحد به‌عنوان کلید به این معنی است که این افکت تنها یک بار زمانی که composable برای اولین بار راه‌اندازی می‌شود اجرا می‌شود.
    • val googleIdOption: GetGoogleIdOption = ... : یک شی GetGoogleIdOption ایجاد می کند. این شیء نوع اعتبار درخواست شده از Google را پیکربندی می کند.
      • .Builder() : یک الگوی سازنده برای پیکربندی گزینه ها استفاده می شود.
      • .setFilterByAuthorizedAccounts(true) : مشخص می کند که آیا به کاربر اجازه می دهد از بین تمام حساب های Google یا فقط حساب هایی که قبلاً برنامه را مجاز کرده اند، انتخاب کند. در این حالت، روی true تنظیم می‌شود، به این معنی که درخواست با استفاده از اعتبارنامه‌هایی که کاربر قبلاً برای استفاده با این برنامه مجوز داده است، در صورت موجود بودن، انجام می‌شود.
      • .setServerClientId(webClientId) : شناسه سرویس گیرنده سرور را تنظیم می کند که یک شناسه منحصر به فرد برای باطن برنامه شما است. این مورد برای دریافت شناسه شناسه الزامی است.
      • .setNonce(generateSecureRandomNonce()) : یک nonce، یک مقدار تصادفی را برای جلوگیری از حملات مجدد و اطمینان از مرتبط بودن رمز ID با درخواست خاص تنظیم می کند.
      • .build() : شی GetGoogleIdOption را با پیکربندی مشخص شده ایجاد می کند.
    • val request: GetCredentialRequest = ... : یک شی GetCredentialRequest ایجاد می کند. این شیء کل درخواست اعتبار را کپسوله می کند.
      • .Builder() : الگوی سازنده را برای پیکربندی درخواست شروع می کند.
      • .addCredentialOption(googleIdOption) : googleIdOption را به درخواست اضافه می کند و مشخص می کند که ما می خواهیم یک رمز Google ID درخواست کنیم.
      • .build() : شی GetCredentialRequest را ایجاد می کند.
    • val e = signIn(request, context) : این تلاش برای ورود کاربر با درخواست ایجاد شده و زمینه فعلی است. نتیجه تابع signIn در e ذخیره می شود. این متغیر شامل یا نتیجه موفقیت آمیز یا یک استثنا خواهد بود.
    • if (e is NoCredentialException) { ... } : این یک بررسی مشروط است. اگر تابع signIn با NoCredentialException ناموفق باشد، به این معنی است که هیچ حساب قبلاً مجاز در دسترس نیست.
      • val googleIdOptionFalse: GetGoogleIdOption = ... : اگر signIn قبلی ناموفق بود، این بخش یک GetGoogleIdOption جدید ایجاد می کند.
      • .setFilterByAuthorizedAccounts(false) : این تفاوت اساسی با گزینه اول است. فیلتر کردن حساب‌های مجاز را غیرفعال می‌کند، به این معنی که از هر حساب Google در دستگاه می‌توان برای ورود استفاده کرد.
      • val requestFalse: GetCredentialRequest = ... : یک GetCredentialRequest جدید با googleIdOptionFalse ایجاد می شود.
      • signIn(requestFalse, context) : این تلاش می‌کند تا کاربر را با درخواست جدیدی که اجازه استفاده از هر حسابی را می‌دهد به سیستم وارد کند.

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

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

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

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

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

اکنون که می‌توانیم درخواست ورود به سیستم خود را بسازیم، می‌توانیم از 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? : این یک تابع تعلیق به نام signIn را تعریف می کند. این بدان معناست که می‌توان بدون مسدود کردن رشته اصلی مکث کرد و از سر گرفت. آیا یک Exception? که در صورت موفقیت آمیز بودن ورود به سیستم باطل خواهد بود یا در صورت عدم موفقیت ورود، استثناء خاص.

دو پارامتر نیاز دارد:

  • request : یک شی GetCredentialRequest ، که حاوی پیکربندی برای نوع اعتبار برای بازیابی است (به عنوان مثال، Google ID).
  • context : زمینه Android مورد نیاز برای تعامل با سیستم.

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

  • val credentialManager = CredentialManager.create(context) : نمونه ای از CredentialManager را ایجاد می کند که رابط اصلی برای تعامل با Credential Manager API است. به این ترتیب برنامه جریان ورود به سیستم را شروع می کند.
  • val failureMessage = "Sign in failed!" : رشته ای (failureMessage) را تعریف می کند که در صورت شکست ورود به سیستم در یک نان تست نمایش داده شود.
  • var e: Exception? = null : این خط یک متغیر e را مقداردهی اولیه می کند تا هر استثنایی که ممکن است در طول فرآیند اتفاق بیفتد را ذخیره کند و با null شروع شود.
  • delay(250) : تاخیر 250 میلی ثانیه ای را معرفی می کند. این یک راه حل برای یک مشکل بالقوه است که در آن NoCredentialException ممکن است بلافاصله هنگام شروع برنامه پرتاب شود، به خصوص هنگام استفاده از جریان BottomSheet. این به سیستم زمان می دهد تا مدیر اعتبار را مقداردهی اولیه کند.
  • try { ... } catch (e: Exception) { ... } :یک بلوک try-catch برای مدیریت قوی خطا استفاده می شود. این تضمین می‌کند که اگر در طول فرآیند ورود به سیستم خطایی رخ دهد، برنامه خراب نمی‌شود و می‌تواند استثنا را به خوبی مدیریت کند.
    • val result = credentialManager.getCredential(request = request, context = context) : اینجا جایی است که تماس واقعی با Credential Manager API اتفاق می‌افتد و فرآیند بازیابی اعتبار را آغاز می‌کند. این درخواست و زمینه را به عنوان ورودی می گیرد و یک رابط کاربری برای انتخاب یک اعتبار به کاربر ارائه می دهد. در صورت موفقیت آمیز بودن، نتیجه ای حاوی اعتبار انتخاب شده را برمی گرداند. نتیجه این عملیات، 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) : استثناهایی را که هنگام تجزیه شناسه شناسه Google خطایی رخ می دهد کنترل می کند.
    • catch (e: NoCredentialException) : NoCredentialException را کنترل می‌کند، که وقتی هیچ اعتبارنامه‌ای برای کاربر در دسترس نباشد (مثلاً هیچ اعتبارنامه‌ای را ذخیره نکرده‌اند یا حساب Google ندارند) پرتاب می‌شود.
      • مهمتر از همه، این تابع استثنای ذخیره شده در e ، NoCredentialException را برمی گرداند، و به تماس گیرنده اجازه می دهد تا در صورت عدم وجود اعتبار، مورد خاص را مدیریت کند.
    • catch (e: GetCredentialCustomException) : استثناهای سفارشی را که ممکن است توسط ارائه‌دهنده اعتبارنامه ارسال شود را کنترل می‌کند.
    • catch (e: GetCredentialCancellationException) : GetCredentialCancellationException را کنترل می کند که زمانی که کاربر فرآیند ورود به سیستم را لغو می کند پرتاب می شود.
    • Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show() : یک پیام نان تست را نمایش می دهد که نشان می دهد ورود به سیستم با استفاده از شکستMessage انجام نشد.
    • Log.e(TAG, "", e) : با استفاده از Log.e که برای خطاها استفاده می شود، استثنای لاگ کت اندروید را ثبت می کند. این شامل stacktrace استثنا برای کمک به اشکال زدایی می شود. همچنین شامل شکلک عصبانی برای سرگرمی است.
  • return e : این تابع در صورتی که موردی گیرافتاده بود، استثنا را برمی‌گرداند، یا اگر ورود موفقیت آمیز بود، صفر را برمی‌گرداند.

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

جریان صفحه پایین را در برنامه پیاده سازی کنید

اکنون می‌توانیم با استفاده از کد زیر و Web Application Client ID که قبلاً از Google Cloud Console کپی کرده‌ایم، تماسی برای راه‌اندازی جریان 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. دکمه run را فشار دهید:پروژه را اجرا کنید
  2. هنگامی که برنامه شما روی شبیه ساز اجرا می شود، باید صفحه پایین ورود به سیستم را مشاهده کنید. ادامه دهید و روی Continue کلیک کنید تا ورود به سیستم را آزمایش کنیدبرگه پایین
  3. باید پیام Toast را ببینید که نشان می دهد ورود با موفقیت انجام شد! موفقیت در برگه پایین

7. جریان دکمه

دکمه Flow GIF

Button Flow for Sign in with Google، ثبت نام یا ورود کاربران به برنامه Android خود را با استفاده از حساب Google موجود آسان‌تر می‌کند. اگر برگه پایینی را رد کنند یا فقط ترجیح دهند صریحاً از حساب Google خود برای ورود به سیستم یا ثبت نام استفاده کنند، به آن ضربه می زنند. برای توسعه دهندگان، این به معنای نصب نرم تر و اصطکاک کمتر در هنگام ثبت نام است.

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

نماد نام تجاری را به پروژه اضافه کنید

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

کد جریان دکمه

این کد از همان تابع signIn() استفاده می‌کند که برای BottomSheet() استفاده می‌شود، اما از GetSignInWithGoogleOption به جای GetGoogleIdOption استفاده می‌کند زیرا این جریان از اعتبارنامه‌ها و کلیدهای عبور ذخیره شده در دستگاه برای نمایش گزینه‌های ورود استفاده نمی‌کند. در اینجا کدی است که می توانید در زیر تابع 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 : زمینه Android فعلی را بازیابی می کند. این برای عملیات های مختلف، از جمله راه اندازی مؤلفه های UI مورد نیاز است.

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

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

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

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

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

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

اکنون باید کلاس 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. دکمه run را فشار دهید:پروژه را اجرا کنید
  2. هنگامی که برنامه ها روی شبیه ساز اجرا می شوند، BottomSheet باید نمایش داده شود. خارج از آن کلیک کنید تا ببندید.اینجا ضربه بزنید
  3. اکنون باید دکمه ای را که ایجاد کردیم در برنامه نمایش داده شود ببینید. پیش بروید و روی آن کلیک کنید تا گفتگوی ورود به سیستم را ببینیدوارد دیالوگ شوید
  4. برای ورود به حساب کاربری خود کلیک کنید!

8. نتیجه گیری

شما این کد لبه را تمام کردید! برای اطلاعات بیشتر یا راهنمایی در مورد ورود به سیستم با Google در Android، بخش سؤالات متداول زیر را ببینید:

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

کد کامل 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
}