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

1. قبل البدء

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

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

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

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

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

ما تحتاج إليه

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

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

للبدء، علينا إنشاء مشروع جديد في "استوديو Android" باتّباع الخطوات التالية:

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

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

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

  1. انتقِل إلى Google Cloud Console.
  2. افتح مشروعك أو أنشئ مشروعًا جديدًاإنشاء مشروع جديد على Google Cloud PlatformGCP 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 تديرها.
    معلومات تطبيق GCP
    • الجمهور:
      • داخلي لتطبيق يُستخدم داخل مؤسستك فقط إذا لم تكن لديك مؤسسة مرتبطة بمشروع Google Cloud، لن تتمكّن من اختيار هذا الخيار.
      • سنستخدم External.
    GCP Audience
    • معلومات الاتصال: يمكن أن يتضمّن هذا الحقل أي عنوان بريد إلكتروني تريد استخدامه كجهة اتصال للتطبيق.
    معلومات الاتصال في Google Cloud Platform
    • راجِع سياسة بيانات المستخدمين في خدمات Google API.
  6. بعد مراجعة سياسة بيانات المستخدمين والموافقة عليها، انقر على إنشاءGCP Create

إعداد عملاء 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 وفتح الوحدة الطرفية
    2. نفِّذ الأمر التالي: Mac/Linux:
      keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
      
      Windows:
        keytool -list -v -keystore "C:\Users\USERNAME\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
      
      تم تصميم هذا الأمر لإدراج تفاصيل إدخال معيّن (اسم مستعار) في ملف تخزين المفاتيح.
      • -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" لإنشاء تطبيق Android الذي يتيح تسجيل الدخول باستخدام حساب Google.

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

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

إنشاء الجهاز الافتراضي المتوافق مع Android

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