كيفية تنفيذ ميزة "تسجيل الدخول باستخدام حساب Google" في تطبيق Android

1. قبل البدء

في هذا الدرس التطبيقي حول الترميز، ستتعرّف على كيفية تنفيذ ميزة "تسجيل الدخول باستخدام Google" على Android باستخدام "مدير بيانات الاعتماد".

المتطلبات الأساسية

  • فهم أساسي لاستخدام Kotlin لتطوير تطبيقات Android
  • فهم أساسي لـ Jetpack Compose (يمكنك الاطّلاع على مزيد من المعلومات هنا)

أهداف الدورة التعليمية

  • كيفية إنشاء مشروع على Google Cloud
  • كيفية إنشاء برامج تعتمد على بروتوكول OAuth في Google Cloud Console
  • كيفية تنفيذ ميزة "تسجيل الدخول باستخدام حساب Google" باستخدام مسار "ورقة أسفل الشاشة"
  • كيفية تنفيذ ميزة "تسجيل الدخول باستخدام حساب Google" من خلال مسار الزر

ما تحتاج إليه

2. إنشاء مشروع في "استوديو Android"

المدة 3:00 - 5:00

للبدء، علينا إنشاء مشروع جديد في Android Studio:

  1. فتح "استوديو Android"
  2. انقر على مشروع جديدمرحبًا بك في "استوديو Android".
  3. اختَر الهاتف والجهاز اللوحي ونشاط فارغمشروع "استوديو Android"
  4. انقر على Next (التالي).
  5. حان الآن وقت إعداد بعض أجزاء المشروع:
    • الاسم: هذا هو اسم مشروعك
    • اسم الحزمة: ستتم تعبئة هذا الحقل تلقائيًا استنادًا إلى اسم مشروعك
    • موقع الحفظ: يجب أن يكون هذا الموقع هو المجلد التلقائي الذي يحفظ فيه "استوديو Android" مشاريعك. يمكنك تغيير ذلك إلى أي مكان تريده.
    • الحد الأدنى لإصدار حزمة تطوير البرامج (SDK): هو أدنى إصدار من حزمة تطوير البرامج لنظام التشغيل Android تم تصميم تطبيقك ليعمل عليه. في هذا الدرس التطبيقي، سنستخدم الإصدار 36 من واجهة برمجة التطبيقات (Baklava).
    مشروع إعداد "استوديو Android"
  6. انقر على إنهاء.
  7. سينشئ "استوديو Android" المشروع وينزّل أي تبعيات ضرورية للتطبيق الأساسي، وقد يستغرق ذلك عدة دقائق. للاطّلاع على ذلك، ما عليك سوى النقر على رمز الإنشاء:إنشاء مشروع في "استوديو Android"
  8. بعد اكتمال ذلك، من المفترض أن يظهر Android Studio على النحو التالي:إنشاء مشروع في "استوديو Android"

3- إعداد مشروعك على Google Cloud

إنشاء مشروع على Google Cloud

  1. انتقِل إلى Google Cloud Console.
  2. افتح مشروعك أو أنشئ مشروعًا جديدًاGCP create new projectGCP create new project 2GCP create new project 3
  3. انقر على واجهات برمجة التطبيقات والخدماتخدمات وواجهات برمجة التطبيقات في Google Cloud Platform
  4. انتقِل إلى شاشة طلب الموافقة المتعلّقة ببروتوكول OAuthشاشة طلب الموافقة المتعلّقة ببروتوكول OAuth في Google Cloud Platform
  5. عليك ملء الحقول في نظرة عامة للمتابعة. انقر على البدء لبدء ملء هذه المعلومات:زر "البدء" في Google Cloud Platform
    • اسم التطبيق: اسم هذا التطبيق، ويجب أن يكون هو نفسه الاسم الذي استخدمته عند إنشاء المشروع في "استوديو Android"
    • البريد الإلكتروني لدعم المستخدمين: سيتم عرض حساب Google الذي تسجّل الدخول باستخدامه وأي مجموعات Google تديرها.
    معلومات تطبيق Google Cloud Platform
    • الجمهور:
      • داخلي لتطبيق يُستخدم داخل مؤسستك فقط إذا لم تكن لديك مؤسسة مرتبطة بمشروع Google Cloud، لن تتمكّن من اختيار هذا المشروع.
      • سنستخدم الخيار "خارجي".
    شريحة جمهور Google Cloud Platform
    • معلومات الاتصال: يمكن أن يتضمّن هذا الحقل أي عنوان بريد إلكتروني تريد استخدامه كجهة اتصال للتطبيق.
    معلومات الاتصال في Google Cloud Platform
    • راجِع خدمات Google API: سياسة بيانات المستخدمين.
  6. بعد مراجعة سياسة بيانات المستخدمين والموافقة عليها، انقر على إنشاءإنشاء Google Cloud Platform

إعداد عملاء OAuth

بعد إعداد مشروع على Google Cloud، علينا إضافة عميل ويب وعميل Android حتى نتمكّن من إجراء طلبات إلى خادم الخلفية OAuth باستخدام معرّفات العملاء.

بالنسبة إلى "عميل الويب" على Android، ستحتاج إلى ما يلي:

  • اسم حزمة تطبيقك (مثلاً com.example.example)
  • توقيع SHA-1 لتطبيقك
    • ما هو توقيع SHA-1؟
      • الملف المرجعي SHA-1 هو تجزئة تشفير يتم إنشاؤها من مفتاح توقيع تطبيقك. ويعمل كمعرّف فريد لشهادة توقيع تطبيقك المحدّد. يمكنك اعتبارها بمثابة "توقيع" رقمي لتطبيقك.
    • لماذا نحتاج إلى توقيع SHA-1؟
      • تضمن بصمة SHA-1 ألا يتمكّن سوى تطبيقك، الذي تم توقيعه باستخدام مفتاح التوقيع المحدّد، من طلب رموز الدخول باستخدام معرّف عميل OAuth 2.0، ما يمنع التطبيقات الأخرى (حتى تلك التي تحمل اسم الحزمة نفسه) من الوصول إلى موارد مشروعك وبيانات المستخدمين.
      • يمكنك التفكير في الأمر على النحو التالي:
        • مفتاح توقيع التطبيق يشبه المفتاح الفعلي "لباب" تطبيقك. وهو ما يتيح الوصول إلى العمليات الداخلية للتطبيق.
        • الملف المرجعي لشهادة SHA-1 هو بمثابة رقم تعريف فريد لبطاقة المفتاح المرتبط بمفتاحك الفعلي. وهو رمز محدّد يعرّف هذا المفتاح.
        • معرّف عميل OAuth 2.0 هو بمثابة رمز دخول إلى خدمة أو مورد معيّنَين من Google (مثل "تسجيل الدخول باستخدام حساب Google").
        • عند تقديم بصمة SHA-1 أثناء إعداد عميل OAuth، فإنّك في الأساس تخبر Google بما يلي: "لا يمكن إلا لبطاقة المفتاح التي تحمل هذا المعرّف المحدّد (SHA-1) فتح رمز الوصول هذا (معرّف العميل)". ويضمن ذلك ألا يتمكّن سوى تطبيقك من الوصول إلى خدمات Google المرتبطة برمز الدخول هذا".

بالنسبة إلى "عميل الويب"، كل ما نحتاج إليه هو الاسم الذي تريد استخدامه لتحديد العميل في وحدة التحكّم.

إنشاء عميل OAuth 2.0 لنظام التشغيل Android

  1. انتقِل إلى صفحة العملاءعملاء Google Cloud Platform
  2. انقر على إنشاء عميلإنشاء عملاء Google Cloud Platform
  3. اختَر Android لنوع التطبيق.
  4. عليك تحديد اسم حزمة تطبيقك
  5. من "استوديو Android"، علينا الحصول على توقيع SHA-1 لتطبيقنا ونسخه ولصقه هنا:
    1. الانتقال إلى Android Studio وفتح الوحدة الطرفية
    2. نفِّذ الأمر التالي:
      keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
      
      تم تصميم هذا الأمر لإدراج تفاصيل إدخال (اسم مستعار) معيّن في ملف تخزين المفاتيح.
      • -list: يطلب هذا الخيار من أداة keytool إدراج محتويات ملف تخزين المفاتيح.
      • -v: يتيح هذا الخيار إخراجًا مفصّلاً، ما يوفّر معلومات أكثر تفصيلاً حول الإدخال.
      • -keystore ~/.android/debug.keystore: يحدّد هذا الخيار مسار ملف تخزين المفاتيح.
      • -alias androiddebugkey: يحدّد هذا الخيار الاسم المستعار (اسم الإدخال) للمفتاح الذي تريد فحصه.
      • -storepass android: يوفّر هذا الحقل كلمة المرور لملف تخزين المفاتيح.
      • -keypass android: يوفّر هذا الخيار كلمة المرور للمفتاح الخاص للاسم المستعار المحدّد.
    3. انسخ قيمة توقيع SHA-1:
    توقيع SHA
    1. ارجع إلى نافذة Google Cloud والصِق قيمة توقيع SHA-1:
  6. يُفترض أن تبدو شاشتك الآن على النحو التالي، ويمكنك النقر على إنشاء:تفاصيل عميل Androidعميل Android

إنشاء عميل OAuth 2.0 على الويب

  1. لإنشاء معرّف عميل لتطبيق ويب، كرِّر الخطوتَين 1 و2 من قسم إنشاء عميل Android واختَر تطبيق ويب في "نوع التطبيق".
  2. امنح العميل اسمًا (سيكون هذا هو عميل OAuth): تفاصيل برنامج الويب
  3. انقر على إنشاءبرنامج الويب
  4. انسخ معرّف العميل من النافذة المنبثقة، ستحتاج إليه لاحقًانسخ معرّف العميل

بعد إعداد جميع عملاء OAuth، يمكننا الرجوع إلى Android Studio لإنشاء تطبيق Android الذي يتيح تسجيل الدخول باستخدام Google.

4. إعداد "الجهاز الافتراضي المتوافق مع Android"

لإجراء اختبار سريع لتطبيقك بدون جهاز Android فعلي، عليك إنشاء جهاز Android افتراضي يمكنك إنشاء تطبيقك وتشغيله عليه على الفور من Android Studio. إذا كنت تريد إجراء الاختبار باستخدام جهاز Android فعلي، يمكنك اتّباع التعليمات الواردة في مستندات مطوّري تطبيقات Android.

إنشاء جهاز Android افتراضي

  1. في Android Studio، افتح "أداة إدارة الأجهزة"مدير الجهاز
  2. انقر على الزر + > إنشاء جهاز افتراضيإنشاء جهاز افتراضي
  3. من هنا، يمكنك إضافة أي جهاز تحتاجه لمشروعك. لأغراض هذا الدرس التطبيقي العملي، اختَر هاتف متوسط، ثم انقر على التاليهاتف متوسط
  4. يمكنك الآن ضبط الجهاز لمشروعك من خلال منح الجهاز اسمًا فريدًا واختيار إصدار Android الذي سيتم تشغيله على الجهاز وغير ذلك. تأكَّد من ضبط واجهة برمجة التطبيقات على API 36 "Baklava"؛ Android 16، ثم انقر على إنهاءإعداد الجهاز الافتراضي
  5. من المفترض أن يظهر الجهاز الجديد في "إدارة الأجهزة". للتأكّد من أنّ الجهاز يعمل، انقر على تشغيل الجهاز بجانب الجهاز الذي أنشأته للتوتشغيل الجهاز 2.
  6. من المفترض أن يكون الجهاز قيد التشغيل الآن.تشغيل الجهاز

تسجيل الدخول إلى "جهاز Android الافتراضي"

الجهاز الذي أنشأته للتو يعمل، والآن لمنع حدوث أخطاء عند اختبار ميزة "تسجيل الدخول باستخدام حساب Google"، عليك تسجيل الدخول إلى الجهاز باستخدام حساب Google.

  1. انتقِل إلى "الإعدادات" باتّباع الخطوات التالية:
    1. انقر على وسط الشاشة على الجهاز الافتراضي ومرِّر سريعًا للأعلى
    النقر والتمرير سريعًا
    1. ابحث عن تطبيق "الإعدادات" وانقر عليه
    تطبيق "الإعدادات"
  2. انقر على Google في "الإعدادات"خدمات Google والإعدادات المفضَّلة
  3. انقر على تسجيل الدخول واتّبِع التعليمات لتسجيل الدخول إلى حسابك على Googleتسجيل الدخول إلى الجهاز
  1. من المفترض أن تكون قد سجّلت الدخول الآن على الجهازتم تسجيل الدخول إلى الجهاز

أصبح "جهاز Android الافتراضي" جاهزًا الآن للاختبار.

5- إضافة عناصر تابعة

المدة 5:00

لإجراء طلبات إلى واجهة برمجة تطبيقات OAuth، علينا أولاً دمج المكتبات اللازمة التي تتيح لنا إجراء طلبات المصادقة واستخدام معرّفات Google لإجراء هذه الطلبات:

  • libs.googleid
  • libs.play.services.auth
  1. انتقِل إلى "ملف" (File) > "بنية المشروع" (Project Structure):بنية المشروع
  2. بعد ذلك، انتقِل إلى التبعيات > التطبيق > '+' > تبعية المكتبةالتبعيات
  3. الآن، علينا إضافة مكتباتنا:
    1. في مربّع حوار البحث، اكتب googleid وانقر على بحث.
    2. يجب أن يكون هناك إدخال واحد فقط، لذا اختَر الإدخال وأحدث إصدار متاح (في وقت إنشاء هذا الدرس العملي، كان الإصدار 1.1.1).
    3. انقر على حسنًاحزمة مستندات التعريف من Google
    4. كرِّر الخطوات من 1 إلى 3، ولكن ابحث بدلاً من ذلك عن "play-services-auth" واختَر السطر الذي يتضمّن "com.google.android.gms" كـ معرّف المجموعة و "play-services-auth" كـ اسم العنصرمصادقة "خدمات Play"
  4. انقر على حسنًاالمهام التابعة المكتملة

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. بعد ذلك، علينا إنشاء الدالة الخاصة بنا لإنشاء طلب "ورقة البيانات السفلية". ألصِق هذا الرمز أسفل فئة 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 الحالي. وهو مطلوب لإجراء عمليات مختلفة، بما في ذلك تشغيل مكوّنات واجهة المستخدِم.
  • LaunchedEffect(Unit) { ... }: LaunchedEffect هي عنصر قابل للإنشاء في Jetpack Compose يتيح لك تنفيذ دالة تعليق (دالة يمكنها إيقاف التنفيذ مؤقتًا واستئنافه) ضمن دورة حياة العنصر القابل للإنشاء. يعني استخدام الوحدة كمفتاح أنّ هذا التأثير لن يتم تنفيذه إلا مرة واحدة عند تشغيل العنصر القابل للإنشاء لأول مرة.
    • val googleIdOption: GetGoogleIdOption = ...: تنشئ عنصر GetGoogleIdOption. يضبط هذا العنصر نوع بيانات الاعتماد المطلوب الحصول عليها من Google.
      • .Builder(): يتم استخدام نمط أداة الإنشاء لضبط الخيارات.
      • .setFilterByAuthorizedAccounts(true): تحدّد ما إذا كان سيُسمح للمستخدم بالاختيار من بين جميع حسابات Google أو الحسابات التي سبق لها منح الإذن للتطبيق. في هذه الحالة، تم ضبطها على "صحيح"، ما يعني أنّه سيتم تقديم الطلب باستخدام بيانات الاعتماد التي سبق أن منح المستخدم الإذن باستخدامها مع هذا التطبيق، إذا كانت أي بيانات متاحة.
      • .setServerClientId(webClientId): يضبط معرّف العميل للخادم، وهو معرّف فريد لخادم الخلفية في تطبيقك. هذا الإجراء مطلوب للحصول على رمز مميّز للمعرّف.
      • .setNonce(generateSecureRandomNonce()): تضبط قيمة عشوائية (nonce) لمنع هجمات إعادة الإرسال والتأكّد من أنّ رمز التعريف المميّز مرتبط بالطلب المحدّد.
      • .build(): تنشئ هذه الطريقة العنصر GetGoogleIdOption باستخدام الإعدادات المحدّدة.
    • val request: GetCredentialRequest = ...: تنشئ عنصر GetCredentialRequest. يحتوي هذا العنصر على طلب بيانات الاعتماد بالكامل.
      • .Builder(): يبدأ نمط أداة الإنشاء لضبط الطلب.
      • .addCredentialOption(googleIdOption): يضيف googleIdOption إلى الطلب، مع تحديد أنّنا نريد طلب رمز مميّز لمعرّف Google.
      • .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 للمستخدم، وذلك باستخدام الإعدادات المقدَّمة. يمكن بعد ذلك استخدام GetCredentialRequest لتشغيل واجهة مستخدم "مدير بيانات الاعتماد"، حيث يمكن للمستخدم اختيار حسابه على Google ومنح الأذونات اللازمة.

fun generateSecureRandomNonce(byteLength: Int = 32): String: يحدّد هذا الرمز دالة باسم generateSecureRandomNonce. تقبل هذه الطريقة وسيطة عدد صحيح byteLength (بقيمة تلقائية تبلغ 32) تحدد الطول المطلوب للرقم العشوائي بالبايت. تعرض هذه الطريقة سلسلة نصية تمثّل وحدات البايت العشوائية بترميز 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 (باستخدام - و_ بدلاً من + و /). وهذا مهم لأنه يضمن إمكانية استخدام السلسلة الناتجة بأمان في عناوين URL بدون الحاجة إلى ترميز إضافي.
    • .withoutPadding(): يؤدي ذلك إلى إزالة أي أحرف حشو من سلسلة Base64 المرمّزة. وغالبًا ما يكون من المستحسن أن يكون الرقم العشوائي أقصر وأكثر إيجازًا.
    • .encodeToString(randomBytes): يرمّز هذا الرمز randomBytes إلى سلسلة Base64 ويعرضها.

باختصار، تنشئ هذه الدالة عددًا عشوائيًا قويًا من الناحية التشفيرية بطول محدّد، وترمّزه باستخدام Base64 الآمن لعناوين URL، وتعرض السلسلة الناتجة. هذه ممارسة معتادة لإنشاء أرقام عشوائية صالحة للاستخدام في السياقات الحسّاسة للأمان.

إرسال طلب تسجيل الدخول

بعد أن أصبح بإمكاننا إنشاء طلب تسجيل الدخول، يمكننا استخدام "مدير بيانات الاعتماد" لتسجيل الدخول. لإجراء ذلك، علينا إنشاء دالة تعالج تمرير طلبات تسجيل الدخول باستخدام 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).
  • 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): هذا هو المكان الذي يتم فيه إجراء طلب البيانات الفعلي من واجهة برمجة تطبيقات "إدارة بيانات الاعتماد" وبدء عملية استرداد بيانات الاعتماد. تتلقّى هذه الواجهة الطلب والسياق كمدخلات، ثم تعرض واجهة مستخدم للمستخدم لاختيار بيانات اعتماد. في حال نجاح العملية، سيتم عرض نتيجة تحتوي على بيانات الاعتماد المحدّدة. يتم تخزين نتيجة هذه العملية، 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(): يعرض رسالة إشعار مؤقت تشير إلى تعذُّر تسجيل الدخول باستخدام failureMessage.
    • Log.e(TAG, "", e): يسجّل الاستثناء في أداة Android logcat باستخدام Log.e، والتي تُستخدَم في تسجيل الأخطاء. سيشمل ذلك تتبُّع تسلسل استدعاء الدوال البرمجية للاستثناء للمساعدة في تصحيح الأخطاء. ويتضمّن أيضًا رمزًا تعبيريًا غاضبًا للمرح.
  • return e: تعرض الدالة الاستثناء إذا تم رصده، أو تعرض قيمة فارغة إذا تم تسجيل الدخول بنجاح.

باختصار، يوفّر هذا الرمز طريقة للتعامل مع تسجيل دخول المستخدم باستخدام Credential Manager API، ويدير العملية غير المتزامنة، ويتعامل مع الأخطاء المحتملة، ويقدّم ملاحظات للمستخدم من خلال الإشعارات المنبثقة والسجلات، مع إضافة لمسة من الفكاهة إلى معالجة الأخطاء.

تنفيذ مسار البطاقة السفلية في التطبيق

يمكننا الآن إعداد طلب لتفعيل مسار BottomSheet في فئة MainActivity باستخدام الرمز التالي ومعرّف عميل تطبيق الويب الذي نسخناه من Google Cloud Console سابقًا:

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

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

        setContent {
            //ExampleTheme - this is derived from the name of the project not any added library
            //e.g. if this project was named "Testing" it would be generated as TestingTheme
            ExampleTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
                ) {
                    //This will trigger on launch
                    BottomSheet(webClientId)
                }
            }
        }
    }
}

يمكننا الآن حفظ مشروعنا (ملف > حفظ) وتشغيله:

  1. انقر على زر التشغيل:تشغيل المشروع
  2. بعد تشغيل تطبيقك على المحاكي، من المفترض أن تظهر النافذة المنبثقة BottomSheet لتسجيل الدخول. انقر على متابعة لاختبار تسجيل الدخولبطاقة سفلية
  3. من المفترض أن تظهر لك رسالة Toast تشير إلى نجاح عملية تسجيل الدخول.نجاح البطاقة السفلية

7. مسار الزر

ملف GIF يوضّح خطوات استخدام الزر

تسهّل "عملية الزر" في ميزة "تسجيل الدخول باستخدام حساب Google" على المستخدمين الاشتراك في تطبيق Android أو تسجيل الدخول إليه باستخدام حسابهم الحالي على Google. سيتم عرضها إذا أغلق المستخدم ورقة البيانات السفلية أو إذا كان يفضّل استخدام حسابه على Google لتسجيل الدخول أو الاشتراك. بالنسبة إلى المطوّرين، يعني ذلك عملية إعداد أكثر سلاسة وتقليل المشاكل أثناء الاشتراك.

على الرغم من إمكانية تنفيذ ذلك باستخدام زر Jetpack Compose جاهز للاستخدام، سنستخدم رمزًا تجاريًا معتمدًا مسبقًا من صفحة إرشادات تصميم ميزة "تسجيل الدخول باستخدام حساب Google".

إضافة رمز العلامة التجارية إلى المشروع

  1. يمكنك تنزيل ملف ZIP الذي يحتوي على رموز العلامات التجارية الموافَق عليها مسبقًا هنا.
  2. فكّ ضغط الملف signin-assest.zip من مجلد التنزيلات (سيختلف هذا الإجراء استنادًا إلى نظام التشغيل على جهاز الكمبيوتر). يمكنك الآن فتح مجلد signin-assets والاطّلاع على الرموز المتاحة. في هذا الدرس التطبيقي حول الترميز، سنستخدم signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png.
  3. نسخ الملف
  4. الصق الملف في المشروع ضمن res > drawable في "استوديو Android" من خلال النقر بزر الماوس الأيمن على مجلد drawable ثم النقر على لصق (قد تحتاج إلى توسيع مجلد res لعرضه)Drawable
  5. سيظهر مربّع حوار يطلب منك إعادة تسمية الملف وتأكيد الدليل الذي ستتم إضافته إليه. أعِد تسمية مادة العرض إلى siwg_button.png، ثم انقر على موافقإضافة زر

رمز تدفّق الزر

سيستخدم هذا الرمز الدالة 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 الحالي. وهو مطلوب لإجراء عمليات مختلفة، بما في ذلك تشغيل مكوّنات واجهة المستخدِم.

val coroutineScope = rememberCoroutineScope(): لإنشاء نطاق كوروتين يُستخدَم هذا الإجراء لإدارة المهام غير المتزامنة، ما يسمح بتشغيل الرمز بدون حظر سلسلة التعليمات الرئيسية. ‫rememberCoroutineScope() هي دالة قابلة للإنشاء من Jetpack Compose توفّر نطاقًا مرتبطًا بدورة حياة العنصر القابل للإنشاء.

val onClick: () -> Unit = { ... }: يؤدي ذلك إلى إنشاء دالة lambda سيتم تنفيذها عند النقر على الزر. ستنفّذ دالة lambda ما يلي:

  • val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(serverClientId = webClientId).setNonce(generateSecureRandomNonce()).build(): ينشئ هذا الجزء عنصر GetSignInWithGoogleOption. يُستخدَم هذا العنصر لتحديد مَعلمات عملية "تسجيل الدخول من خلال Google"، ويتطلّب webClientId وnonce (سلسلة عشوائية تُستخدَم لأغراض الأمان).
  • val request: GetCredentialRequest = GetCredentialRequest.Builder().addCredentialOption(signInWithGoogleOption).build(): يؤدي ذلك إلى إنشاء عنصر GetCredentialRequest. سيتم استخدام هذا الطلب للحصول على بيانات اعتماد المستخدم باستخدام "مدير الاعتماد". يضيف 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): تجعل الصورة قابلة للنقر، وعند النقر عليها، يتم تنفيذ دالة lambda onClick التي تم تحديدها سابقًا.

باختصار، يضبط هذا الرمز زر "تسجيل الدخول باستخدام Google" في واجهة مستخدم Jetpack Compose. عند النقر على الزر، يتم إعداد طلب بيانات اعتماد لتشغيل "مدير بيانات الاعتماد" والسماح للمستخدم بتسجيل الدخول باستخدام حسابه على 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)
                    }
                }
            }
        }
    }
}

يمكننا الآن حفظ مشروعنا (ملف > حفظ) وتشغيله:

  1. انقر على زر التشغيل:تشغيل المشروع
  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
}