۱. قبل از شروع
در این آزمایشگاه کد، نحوه پیادهسازی ورود با گوگل در اندروید با استفاده از Credential Manager را یاد میگیرید.
پیشنیازها
- درک اولیه از استفاده از کاتلین برای توسعه اندروید
- درک اولیه از Jetpack Compose (اطلاعات بیشتر را میتوانید اینجا پیدا کنید)
آنچه یاد خواهید گرفت
- نحوه ایجاد یک پروژه ابری گوگل
- نحوه ایجاد کلاینتهای OAuth در کنسول ابری گوگل
- نحوه پیادهسازی ورود با گوگل با استفاده از جریان برگه پایانی
- نحوه پیادهسازی ورود با گوگل با استفاده از جریان دکمه
آنچه شما نیاز دارید
- اندروید استودیو ( از اینجا دانلود کنید)
- رایانهای که الزامات سیستم اندروید استودیو را برآورده کند
- رایانهای که الزامات سیستم شبیهساز اندروید را برآورده کند
- نصب جاوا و کیت توسعه جاوا (JDK)
۲. یک پروژه اندروید استودیو ایجاد کنید
مدت زمان ۳:۰۰ - ۵:۰۰
برای شروع، باید یک پروژه جدید در اندروید استودیو ایجاد کنیم:
- اندروید استودیو را باز کنید
- روی پروژه جدید کلیک کنید

- تلفن و تبلت را انتخاب کنید و فعالیت را خالی کنید

- روی بعدی کلیک کنید
- حالا وقتشه که چند تا از بخشهای پروژه رو تنظیم کنیم:
- نام : این نام پروژه شماست
- نام بسته : این به طور خودکار بر اساس نام پروژه شما پر میشود.
- محل ذخیره : این مکان به طور پیشفرض باید در پوشهای باشد که اندروید استودیو پروژههای شما را در آن ذخیره میکند. میتوانید این مکان را به هر جایی که دوست دارید تغییر دهید.
- Minimum SDK : این پایینترین نسخه Android SDK است که برنامه شما برای اجرا روی آن ساخته شده است. در این CodeLab، ما از API 36 (Baklava) استفاده خواهیم کرد.

- روی پایان کلیک کنید
- اندروید استودیو پروژه را ایجاد کرده و هرگونه وابستگی لازم برای برنامه پایه را دانلود میکند، این کار میتواند چند دقیقه طول بکشد. برای مشاهده این اتفاق، فقط روی نماد ساخت کلیک کنید:

- پس از اتمام این کار، اندروید استودیو باید شبیه به این باشد:

۳. پروژه گوگل کلود خود را راهاندازی کنید
ایجاد یک پروژه گوگل کلود
- به کنسول ابری گوگل بروید
- پروژه خود را باز کنید یا یک پروژه جدید ایجاد کنید



- روی APIها و سرویسها کلیک کنید

- به صفحه رضایت OAuth بروید

- برای ادامه باید فیلدهای نمای کلی را پر کنید. برای شروع پر کردن این اطلاعات، روی شروع کلیک کنید:

- نام برنامه : نام این برنامه که باید همان نامی باشد که هنگام ایجاد پروژه در اندروید استودیو استفاده کردهاید.
- ایمیل پشتیبانی کاربر : این ایمیل، حساب گوگلی که با آن وارد سیستم شدهاید و هر گروه گوگلی که مدیریت میکنید را نشان میدهد.

- مخاطب :
- داخلی برای برنامهای که فقط در سازمان شما استفاده میشود. اگر سازمانی مرتبط با پروژه گوگل کلود ندارید، نمیتوانید این را انتخاب کنید.
- خارجی چیزی خواهد بود که ما از آن استفاده میکنیم.

- اطلاعات تماس : این میتواند شامل هر ایمیلی باشد که میخواهید به عنوان نقطه تماس برای درخواست باشد.

- بررسی سرویسهای API گوگل: سیاست دادههای کاربر.
- پس از بررسی و موافقت با سیاست دادههای کاربر ، روی ایجاد کلیک کنید.

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

- روی ایجاد کلاینت کلیک کنید

- برای نوع برنامه، اندروید را انتخاب کنید
- شما باید نام بسته برنامه خود را مشخص کنید
- از اندروید استودیو باید امضای SHA-1 برنامه خود را دریافت کرده و آن را اینجا کپی/پیست کنیم:
- به اندروید استودیو بروید و ترمینال را باز کنید.
- این دستور را اجرا کنید: مک/لینوکس:
ویندوز:keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
این دستور برای فهرست کردن جزئیات یک ورودی خاص (نام مستعار) در یک keystore طراحی شده است.keytool -list -v -keystore "C:\Users\USERNAME\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
-
-list: این گزینه به keytool میگوید که محتویات keystore را فهرست کند. -
-v: این گزینه خروجی شفاف (verbose output) را فعال میکند و اطلاعات دقیقتری در مورد ورودی ارائه میدهد. -
-keystore ~/.android/debug.keystore: این مسیر فایل keystore را مشخص میکند. -
-alias androiddebugkey: این نام مستعار (نام ورودی) کلیدی را که میخواهید بررسی کنید، مشخص میکند. -
-storepass android: این گزینه رمز عبور فایل keystore را ارائه میدهد. -
-keypass android: این گزینه رمز عبور کلید خصوصیِ نام مستعار مشخص شده را ارائه میدهد.
-
- مقدار امضای SHA-1 را کپی کنید:

- به پنجره Google Cloud برگردید و مقدار امضای SHA-1 را وارد کنید:
- اکنون صفحه شما باید شبیه به این باشد و میتوانید روی Create کلیک کنید:


ایجاد کلاینت وب OAuth 2.0
- برای ایجاد شناسه کلاینت برنامه وب، مراحل ۱-۲ را از بخش ایجاد کلاینت اندروید تکرار کنید و برای نوع برنامه، برنامه وب را انتخاب کنید.
- به کلاینت یک نام بدهید (این نام، کلاینت OAuth خواهد بود):

- روی ایجاد کلیک کنید

- ادامه دهید و شناسه کلاینت را از پنجره باز شده کپی کنید، بعداً به آن نیاز خواهید داشت

حالا که کلاینتهای OAuth خود را تنظیم کردهایم، میتوانیم به اندروید استودیو برگردیم و برنامهی اندروید Sign in With Google خود را بسازیم!
۴. راهاندازی دستگاه مجازی اندروید
برای تست سریع برنامه خود بدون یک دستگاه اندروید فیزیکی، باید یک دستگاه مجازی اندروید ایجاد کنید که بتوانید برنامه خود را از اندروید استودیو بسازید و بلافاصله روی آن اجرا کنید. اگر میخواهید با یک دستگاه اندروید فیزیکی تست کنید، میتوانید دستورالعملهای موجود در مستندات توسعهدهنده اندروید را دنبال کنید.
ایجاد یک دستگاه مجازی اندروید
- در اندروید استودیو، Device Manager را باز کنید.

- روی دکمه + > ایجاد دستگاه مجازی کلیک کنید

- از اینجا میتوانید هر دستگاهی را که برای پروژه خود نیاز دارید اضافه کنید. برای اهداف این Codelab، Medium Phone را انتخاب کنید و سپس روی Next کلیک کنید.

- حالا میتوانید دستگاه را برای پروژه خود پیکربندی کنید، برای این کار یک نام منحصر به فرد به آن بدهید، نسخه اندروید مورد استفاده دستگاه را انتخاب کنید و موارد دیگر. مطمئن شوید که API روی API 36 "Baklava" تنظیم شده است؛ اندروید ۱۶، سپس روی Finish کلیک کنید.

- شما باید دستگاه جدید را در Device Manager ببینید. برای تأیید اینکه دستگاه در حال اجرا است، ادامه دهید و کلیک کنید
کنار دستگاهی که تازه ایجاد کردهاید
- الان دستگاه باید کار کنه!

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

- برنامه تنظیمات (Settings) را پیدا کنید و روی آن کلیک کنید.

- در تنظیمات روی گوگل کلیک کنید

- روی ورود کلیک کنید و دستورالعملها را دنبال کنید تا به حساب گوگل خود وارد شوید.

- اکنون باید در دستگاه وارد سیستم شده باشید

دستگاه مجازی اندروید شما اکنون آماده آزمایش است!
۵. وابستگیها را اضافه کنید
مدت زمان ۵:۰۰
برای برقراری تماسهای API مربوط به OAuth، ابتدا باید کتابخانههای مورد نیاز را که به ما امکان میدهند درخواستهای احراز هویت را انجام دهیم، ادغام کنیم و از شناسههای گوگل برای انجام این درخواستها استفاده کنیم:
- شناسه گوگل
- libs.play.services.auth
- به فایل > ساختار پروژه بروید:

- سپس به وابستگیها > برنامه > '+' > وابستگی کتابخانه بروید

- حالا باید کتابخانههایمان را اضافه کنیم:
- در کادر جستجو، googleid را تایپ کنید و روی جستجو کلیک کنید.
- فقط باید یک ورودی وجود داشته باشد، آن را انتخاب کنید و بالاترین نسخه موجود را انتخاب کنید (در زمان نگارش این Codelab نسخه ۱.۱.۱ است)
- روی تأیید کلیک کنید

- مراحل ۱ تا ۳ را تکرار کنید، اما به جای آن عبارت "play-services-auth" را جستجو کنید و خطی را که "com.google.android.gms" به عنوان شناسه گروه و "play-services-auth" به عنوان نام مصنوع دارد، انتخاب کنید.

- روی تأیید کلیک کنید

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

جریان برگه پایانی از API مدیریت اعتبارنامه (Credential Manager API) برای روشی سادهتر جهت ورود کاربران به برنامه شما با استفاده از حسابهای گوگل خود در اندروید استفاده میکند . این API به گونهای طراحی شده است که سریع و راحت باشد، به خصوص برای کاربرانی که دوباره از برنامه استفاده میکنند. این جریان باید در هنگام اجرای برنامه فعال شود.
درخواست ورود را بسازید
- برای شروع، توابع
Greeting()وGreetingPreview()را از فایلMainActivity.ktحذف کنید، دیگر به آنها نیازی نخواهیم داشت. - حالا باید مطمئن شویم که بستههای مورد نیاز برای این پروژه وارد شدهاند. دستورات
importزیر را بعد از دستورات موجود از خط ۳ اضافه کنید:import android.content.ContentValues.TAG import android.content.Context import android.credentials.GetCredentialException import android.os.Build import android.util.Log import android.widget.Toast import androidx.annotation.RequiresApi import androidx.compose.foundation.clickable import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.credentials.CredentialManager import androidx.credentials.exceptions.GetCredentialCancellationException import androidx.credentials.exceptions.GetCredentialCustomException import androidx.credentials.exceptions.NoCredentialException import androidx.credentials.GetCredentialRequest import com.google.android.libraries.identity.googleid.GetGoogleIdOption import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException import java.security.SecureRandom import java.util.Base64 import kotlinx.coroutines.CoroutineScope import androidx.compose.runtime.LaunchedEffect import kotlinx.coroutines.delay import kotlinx.coroutines.launch - در مرحله بعد، باید تابع خود را برای ساخت درخواست Bottom Sheet ایجاد کنیم. این کد را در زیر کلاس MainActivity قرار دهید.
//This line is not needed for the project to build, but you will see errors if it is not present.
//This code will not work on Android versions < UpsideDownCake
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun BottomSheet(webClientId: String) {
val context = LocalContext.current
// LaunchedEffect is used to run a suspend function when the composable is first launched.
LaunchedEffect(Unit) {
// Create a Google ID option with filtering by authorized accounts enabled.
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(true)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
// Create a credential request with the Google ID option.
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()
// Attempt to sign in with the created request using an authorized account
val e = signIn(request, context)
// If the sign-in fails with NoCredentialException, there are no authorized accounts.
// In this case, we attempt to sign in again with filtering disabled.
if (e is NoCredentialException) {
val googleIdOptionFalse: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(false)
.setServerClientId(webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val requestFalse: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOptionFalse)
.build()
//We will build out this function in a moment
signIn(requestFalse, context)
}
}
}
//This function is used to generate a secure nonce to pass in with our request
fun generateSecureRandomNonce(byteLength: Int = 32): String {
val randomBytes = ByteArray(byteLength)
SecureRandom.getInstanceStrong().nextBytes(randomBytes)
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
}
بیایید ببینیم این کد چه کاری انجام میدهد:
fun BottomSheet(webClientId: String) {...} : تابعی به نام BottomSheet ایجاد میکند که یک آرگومان رشتهای به نام webClientid دریافت میکند.
-
val context = LocalContext.current: زمینه فعلی اندروید را بازیابی میکند. این برای عملیات مختلف، از جمله راهاندازی اجزای رابط کاربری، مورد نیاز است. -
LaunchedEffect(Unit) { ... }:LaunchedEffectیک Composable از Jetpack Compose است که به شما امکان میدهد یک تابع suspend (تابعی که میتواند اجرا را متوقف کرده و از سر بگیرد) را در چرخه حیات Composable اجرا کنید. واحد به عنوان کلید به این معنی است که این effect فقط یک بار و در اولین اجرای Composable اجرا میشود.-
val googleIdOption: GetGoogleIdOption = ...: یک شیءGetGoogleIdOptionایجاد میکند. این شیء نوع اعتبارنامهای که از گوگل درخواست میشود را پیکربندی میکند.-
.Builder(): از الگوی سازنده برای پیکربندی گزینهها استفاده میشود. -
.setFilterByAuthorizedAccounts(true): مشخص میکند که آیا به کاربر اجازه داده شود از بین همه حسابهای گوگل انتخاب کند یا فقط حسابهایی که قبلاً برنامه را مجاز کردهاند. در این حالت، روی true تنظیم شده است، به این معنی که درخواست با استفاده از اعتبارنامههایی که کاربر قبلاً برای استفاده با این برنامه مجاز کرده است، در صورت وجود، انجام میشود. -
.setServerClientId(webClientId): شناسه کلاینت سرور را تنظیم میکند، که یک شناسه منحصر به فرد برای backend برنامه شما است. این برای دریافت توکن شناسه لازم است. -
.setNonce(generateSecureRandomNonce()): یک nonce، یک مقدار تصادفی، برای جلوگیری از حملات بازپخش و اطمینان از مرتبط بودن توکن شناسه با درخواست خاص، تنظیم میکند. -
.build(): شیءGetGoogleIdOptionرا با پیکربندی مشخص شده ایجاد میکند.
-
-
val request: GetCredentialRequest = ...: یک شیءGetCredentialRequestایجاد میکند. این شیء کل درخواست اعتبارنامه را کپسولهسازی میکند.-
.Builder(): الگوی سازنده را برای پیکربندی درخواست آغاز میکند. -
.addCredentialOption(googleIdOption): googleIdOption را به درخواست اضافه میکند و مشخص میکند که ما میخواهیم یک توکن شناسه گوگل درخواست کنیم. -
.build(): شیءGetCredentialRequestرا ایجاد میکند.
-
-
val e = signIn(request, context): این تابع تلاش میکند تا کاربر را با درخواست ایجاد شده و زمینه فعلی وارد سیستم کند. نتیجه تابع signIn در e ذخیره میشود. این متغیر یا حاوی نتیجه موفقیتآمیز یا حاوی یک استثنا خواهد بود. -
if (e is NoCredentialException) { ... }: این یک بررسی شرطی است. اگر تابع signIn با خطای NoCredentialException مواجه شود، به این معنی است که هیچ حساب کاربری قبلاً مجاز شدهای در دسترس نیست.-
val googleIdOptionFalse: GetGoogleIdOption = ...: اگرsignInقبلی ناموفق بود، این بخش یکGetGoogleIdOptionجدید ایجاد میکند. -
.setFilterByAuthorizedAccounts(false): این تفاوت اساسی با گزینه اول است. این گزینه فیلتر کردن حسابهای مجاز را غیرفعال میکند، به این معنی که هر حساب گوگلی روی دستگاه میتواند برای ورود به سیستم استفاده شود. -
val requestFalse: GetCredentialRequest = ...: یکGetCredentialRequestجدید باgoogleIdOptionFalseایجاد میشود. -
signIn(requestFalse, context): این متد تلاش میکند تا کاربر را با درخواست جدیدی که اجازه استفاده از هر حسابی را میدهد، وارد سیستم کند.
-
-
در اصل، این کد با استفاده از پیکربندیهای ارائه شده، درخواستی را به API مدیریت اعتبارنامه (Credential Manager API) ارسال میکند تا یک توکن شناسه گوگل (Google ID token) برای کاربر بازیابی کند. سپس میتوان از GetCredentialRequest برای راهاندازی رابط کاربری مدیریت اعتبارنامه استفاده کرد، جایی که کاربر میتواند حساب گوگل خود را انتخاب کرده و مجوزهای لازم را اعطا کند.
fun generateSecureRandomNonce(byteLength: Int = 32): String : این تابعی به نام generateSecureRandomNonce تعریف میکند. این تابع یک آرگومان عدد صحیح byteLength (با مقدار پیشفرض ۳۲) را میپذیرد که طول مطلوب nonce را بر حسب بایت مشخص میکند. این تابع یک رشته برمیگرداند که نمایش کدگذاری شده Base64 از بایتهای تصادفی خواهد بود.
-
val randomBytes = ByteArray(byteLength): یک آرایه بایتی از byteLength مشخص شده برای نگهداری بایتهای تصادفی ایجاد میکند. -
SecureRandom.getInstanceStrong().nextBytes(randomBytes):-
SecureRandom.getInstanceStrong(): این یک مولد اعداد تصادفی قوی از نظر رمزنگاری به دست میآورد. این برای امنیت بسیار مهم است، زیرا تضمین میکند که اعداد تولید شده واقعاً تصادفی و غیرقابل پیشبینی هستند. این تابع از قویترین منبع آنتروپی موجود در سیستم استفاده میکند. -
.nextBytes(randomBytes): این تابع آرایه randomBytes را با بایتهای تصادفی تولید شده توسط نمونه SecureRandom پر میکند.
-
-
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)-
Base64.getUrlEncoder(): این یک رمزگذار Base64 دریافت میکند که از الفبای URL-safe استفاده میکند (استفاده از - و _ به جای + و /). این مهم است زیرا تضمین میکند که رشته حاصل میتواند بدون نیاز به رمزگذاری بیشتر، با خیال راحت در URLها استفاده شود. -
.withoutPadding(): این تابع هرگونه کاراکتر فاصلهگذاری (padding) را از رشته کدگذاری شده Base64 حذف میکند. این اغلب برای کوتاهتر و فشردهتر کردن nonce مطلوب است. -
.encodeToString(randomBytes): این تابع randomBytes را در یک رشته Base64 کدگذاری کرده و آن را برمیگرداند.
-
به طور خلاصه، این تابع یک نانس تصادفی قوی از نظر رمزنگاری با طول مشخص تولید میکند، آن را با استفاده از Base64 که برای URL ایمن است، کدگذاری میکند و رشته حاصل را برمیگرداند. این یک روش استاندارد برای تولید نانسهایی است که برای استفاده در زمینههای حساس به امنیت ایمن هستند.
درخواست ورود را انجام دهید
اکنون که میتوانیم درخواست ورود به سیستم خود را بسازیم، میتوانیم از Credential Manager برای ورود به سیستم استفاده کنیم. برای انجام این کار، باید تابعی ایجاد کنیم که درخواستهای ورود به سیستم را با استفاده از Credential Manager مدیریت کند، ضمن اینکه استثنائات رایجی را که ممکن است با آنها مواجه شویم، مدیریت کند.
برای انجام این کار، میتوانید این تابع را زیر تابع BottomSheet() قرار دهید.
//This code will not work on Android versions < UPSIDE_DOWN_CAKE when GetCredentialException is
//is thrown.
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
suspend fun signIn(request: GetCredentialRequest, context: Context): Exception? {
val credentialManager = CredentialManager.create(context)
val failureMessage = "Sign in failed!"
var e: Exception? = null
//using delay() here helps prevent NoCredentialException when the BottomSheet Flow is triggered
//on the initial running of our app
delay(250)
try {
// The getCredential is called to request a credential from Credential Manager.
val result = credentialManager.getCredential(
request = request,
context = context,
)
Log.i(TAG, result.toString())
Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
Log.i(TAG, "(☞゚ヮ゚)☞ Sign in Successful! ☜(゚ヮ゚☜)")
} catch (e: GetCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Failure getting credentials", e)
} catch (e: GoogleIdTokenParsingException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with parsing received GoogleIdToken", e)
} catch (e: NoCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": No credentials found", e)
return e
} catch (e: GetCredentialCustomException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with custom credential request", e)
} catch (e: GetCredentialCancellationException) {
Toast.makeText(context, ": Sign-in cancelled", Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Sign-in was cancelled", e)
}
return e
}
حالا به بررسی عملکرد کد در اینجا میپردازیم:
suspend fun signIn(request: GetCredentialRequest, context: Context): Exception? : این یک تابع suspend به نام signIn تعریف میکند. این بدان معناست که میتواند بدون مسدود کردن thread اصلی، متوقف و از سر گرفته شود. این تابع یک Exception? برمیگرداند که در صورت موفقیتآمیز بودن ورود، null و در صورت ناموفق بودن ورود، همان exception خاص را برمیگرداند.
دو پارامتر میگیرد:
-
request: یک شیءGetCredentialRequestکه شامل پیکربندی نوع اعتبارنامهای است که باید بازیابی شود (مثلاً شناسه گوگل). -
context: همان Context اندروید است که برای تعامل با سیستم مورد نیاز است.
برای بدنه تابع:
-
val credentialManager = CredentialManager.create(context): یک نمونه از CredentialManager ایجاد میکند که رابط اصلی برای تعامل با API مربوط به Credential Manager است. برنامه به این ترتیب جریان ورود را آغاز خواهد کرد. -
val failureMessage = "Sign in failed!": یک رشته (failureMessage) تعریف میکند که در صورت عدم موفقیت ورود به سیستم، در یک پیام Toast نمایش داده میشود. -
var e: Exception? = null: این خط متغیر e را برای ذخیره هر استثنایی که ممکن است در طول فرآیند رخ دهد، با شروع از null، مقداردهی اولیه میکند. -
delay(250): یک تأخیر ۲۵۰ میلیثانیهای ایجاد میکند. این یک راه حل برای یک مشکل بالقوه است که در آن ممکن است NoCredentialException بلافاصله هنگام شروع برنامه، به خصوص هنگام استفاده از جریان BottomSheet، رخ دهد. این به سیستم زمان میدهد تا مدیریت اعتبارنامه را مقداردهی اولیه کند. -
try { ... } catch (e: Exception) { ... }: یک بلوک try-catch برای مدیریت قوی خطا استفاده میشود. این تضمین میکند که اگر در طول فرآیند ورود به سیستم خطایی رخ دهد، برنامه از کار نمیافتد و میتواند خطا را به خوبی مدیریت کند.-
val result = credentialManager.getCredential(request = request, context = context): اینجا جایی است که فراخوانی واقعی به API مدیریت اعتبارنامه اتفاق میافتد و فرآیند بازیابی اعتبارنامه را آغاز میکند. این API درخواست و زمینه را به عنوان ورودی دریافت میکند و یک رابط کاربری (UI) برای انتخاب اعتبارنامه به کاربر ارائه میدهد. در صورت موفقیت، نتیجهای حاوی اعتبارنامه انتخاب شده را برمیگرداند. نتیجه این عملیات،GetCredentialResponse، در متغیرresultذخیره میشود. -
Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show(): یک پیام کوتاه مبنی بر موفقیتآمیز بودن ورود به سیستم نمایش میدهد. -
Log.i(TAG, "Sign in Successful!"): یک پیام جالب و موفقیتآمیز را در logcat ثبت میکند. -
catch (e: GetCredentialException): استثنائات از نوعGetCredentialExceptionرا مدیریت میکند. این یک کلاس والد برای چندین استثنای خاص است که میتوانند در طول فرآیند واکشی اعتبارنامه رخ دهند. -
catch (e: GoogleIdTokenParsingException): استثنائاتی را که هنگام خطایی در تجزیه توکن شناسه گوگل رخ میدهند، مدیریت میکند. -
catch (e: NoCredentialException): خطایNoCredentialExceptionرا مدیریت میکند، که زمانی رخ میدهد که هیچ اعتبارنامهای برای کاربر در دسترس نباشد (مثلاً هیچ اعتباری ذخیره نکرده باشد، یا حساب گوگل نداشته باشد).- نکتهی مهم این است که این تابع، استثنای ذخیره شده در
e،NoCredentialException، را برمیگرداند و به فراخواننده اجازه میدهد در صورت عدم وجود اعتبارنامه، مورد خاص را مدیریت کند.
- نکتهی مهم این است که این تابع، استثنای ذخیره شده در
-
catch (e: GetCredentialCustomException): استثنائات سفارشی را که ممکن است توسط ارائه دهنده اعتبارنامه ایجاد شوند، مدیریت میکند. -
catch (e: GetCredentialCancellationException): استثنایGetCredentialCancellationExceptionرا مدیریت میکند، که زمانی که کاربر فرآیند ورود به سیستم را لغو میکند، رخ میدهد. -
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show(): با استفاده از failMessage یک پیام toast نمایش میدهد که نشان میدهد ورود به سیستم ناموفق بوده است. -
Log.e(TAG, "", e): با استفاده از Log.e، استثنا را در logcat اندروید ثبت میکند که برای خطاها استفاده میشود. این شامل stacktrace استثنا برای کمک به اشکالزدایی است. همچنین شامل شکلک عصبانی برای سرگرمی است.
-
-
return e: این تابع در صورت بروز هرگونه خطا، آن را برمیگرداند و در صورت موفقیتآمیز بودن ورود، مقدار null را برمیگرداند.
به طور خلاصه، این کد روشی برای مدیریت ورود کاربر با استفاده از رابط برنامهنویسی مدیریت اعتبارنامه (Credential Manager API) ارائه میدهد، عملیات ناهمزمان را مدیریت میکند، خطاهای احتمالی را مدیریت میکند و از طریق Toastها و Logها به کاربر بازخورد میدهد و در عین حال کمی چاشنی طنز به مدیریت خطا اضافه میکند.
جریان برگه پایانی را روی برنامه پیادهسازی کنید
اکنون میتوانیم با استفاده از کد زیر و شناسه کلاینت برنامه وب خود که قبلاً از کنسول Google Cloud کپی کردهایم، فراخوانی برای فعال کردن جریان BottomSheet در کلاس MainActivity خود تنظیم کنیم:
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//replace with your own web client ID from Google Cloud Console
val webClientId = "YOUR_CLIENT_ID_HERE"
setContent {
//ExampleTheme - this is derived from the name of the project not any added library
//e.g. if this project was named "Testing" it would be generated as TestingTheme
ExampleTheme {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
) {
//This will trigger on launch
BottomSheet(webClientId)
}
}
}
}
}
حالا میتوانیم پروژهمان را ذخیره کنیم ( File > Save ) و آن را اجرا کنیم:
- دکمه اجرا را فشار دهید:

- وقتی برنامه شما روی شبیهساز اجرا شد، باید صفحه ورود به سیستم BottomSheet را ببینید. برای آزمایش ورود به سیستم، روی Continue کلیک کنید.

- شما باید یک پیام Toast ببینید که نشان میدهد ورود به سیستم موفقیتآمیز بوده است!

۷. جریان دکمه

دکمهی ورود با گوگل، ثبت نام یا ورود به برنامهی اندروید شما را با استفاده از حساب گوگل فعلی کاربران آسانتر میکند. اگر کاربران از صفحهی اصلی صرف نظر کنند یا صرفاً ترجیح دهند از حساب گوگل خود برای ورود یا ثبت نام استفاده کنند، با این گزینه مواجه خواهند شد. برای توسعهدهندگان، این به معنای آشنایی روانتر و اصطکاک کمتر در حین ثبت نام است.
اگرچه این کار را میتوان با دکمهی آمادهی Jetpack Compose انجام داد، ما از یک نماد برند از پیش تأیید شده از صفحهی «ورود با دستورالعملهای برندسازی گوگل» استفاده خواهیم کرد.
اضافه کردن آیکون برند به پروژه
- فایل زیپ آیکونهای برند از پیش تایید شده را از اینجا دانلود کنید
- فایل signin-assest.zip را از دانلودهایتان از حالت فشرده خارج کنید (این فایل بسته به سیستم عامل رایانه شما متفاوت خواهد بود). اکنون میتوانید پوشه signin-assets را باز کنید و آیکونهای موجود را بررسی کنید. برای این Codelab، ما از
signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.pngاستفاده خواهیم کرد. - فایل را کپی کنید
- با کلیک راست روی پوشه drawable و انتخاب گزینه Paste ، آن را در پروژه اندروید استودیو و در مسیر res > drawable قرار دهید (ممکن است برای مشاهده آن مجبور شوید پوشه res را باز کنید).

- یک کادر محاورهای نمایش داده میشود که از شما میخواهد نام فایل را تغییر دهید و دایرکتوری که قرار است به آن اضافه شود را تأیید کنید. نام فایل را به siwg_button.png تغییر دهید و سپس روی تأیید کلیک کنید.

کد جریان دکمه
این کد از همان تابع signIn() که برای BottomSheet() استفاده میشود، استفاده میکند، اما به جای GetGoogleIdOption از GetSignInWithGoogleOption استفاده میکند، زیرا این جریان از اعتبارنامهها و کلیدهای عبور ذخیره شده در دستگاه برای نمایش گزینههای ورود استفاده نمیکند. در اینجا کدی وجود دارد که میتوانید آن را زیر تابع BottomSheet() قرار دهید:
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Composable
fun ButtonUI(webClientId: String) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val onClick: () -> Unit = {
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption
.Builder(serverClientId = webClientId)
.setNonce(generateSecureRandomNonce())
.build()
val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(signInWithGoogleOption)
.build()
coroutineScope.launch {
signIn(request, context)
}
}
Image(
painter = painterResource(id = R.drawable.siwg_button),
contentDescription = "",
modifier = Modifier
.fillMaxSize()
.clickable(enabled = true, onClick = onClick)
)
}
برای تجزیه و تحلیل عملکرد کد:
fun ButtonUI(webClientId: String) : این تابعی به نام ButtonUI تعریف میکند که یک webClientId (شناسه کلاینت پروژه Google Cloud شما) را به عنوان آرگومان میپذیرد.
val context = LocalContext.current : زمینه فعلی اندروید را بازیابی میکند. این برای عملیات مختلف، از جمله راهاندازی اجزای رابط کاربری، مورد نیاز است.
val coroutineScope = rememberCoroutineScope() : یک محدوده کوروتین ایجاد میکند. این برای مدیریت وظایف ناهمزمان استفاده میشود و به کد اجازه میدهد بدون مسدود کردن نخ اصلی اجرا شود. rememberCoroutineScope () یک تابع composable از Jetpack Compose است که محدودهای را فراهم میکند که به چرخه حیات composable گره خورده است.
val onClick: () -> Unit = { ... } : این یک تابع لامبدا ایجاد میکند که هنگام کلیک روی دکمه اجرا میشود. تابع لامبدا:
-
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(serverClientId = webClientId).setNonce(generateSecureRandomNonce()).build(): این بخش یک شیءGetSignInWithGoogleOptionایجاد میکند. این شیء برای تعیین پارامترهای فرآیند "ورود با گوگل" استفاده میشود و بهwebClientIdو یک nonce (یک رشته تصادفی که برای امنیت استفاده میشود) نیاز دارد. -
val request: GetCredentialRequest = GetCredentialRequest.Builder().addCredentialOption(signInWithGoogleOption).build(): این یک شیءGetCredentialRequestمیسازد. این درخواست برای دریافت اعتبارنامه کاربر با استفاده از Credential Manager استفاده خواهد شد.GetCredentialRequestGetSignInWithGoogleOptionکه قبلاً ایجاد شده بود را به عنوان یک گزینه اضافه میکند تا اعتبارنامه "ورود با گوگل" را درخواست کند.
-
coroutineScope.launch { ... }: یکCoroutineScopeبرای مدیریت عملیات ناهمزمان (با استفاده از Coroutine).-
signIn(request, context): تابعsignIn() که قبلاً تعریف شده است را فراخوانی میکند.
-
Image(...) : این تابع با استفاده از painterResource که تصویر R.drawable.siwg_button را بارگذاری میکند، یک تصویر را رندر میکند.
-
Modifier.fillMaxSize().clickable(enabled = true, onClick = onClick):-
fillMaxSize()باعث میشود تصویر تمام فضای موجود را پر کند. -
clickable(enabled = true, onClick = onClick): تصویر را قابل کلیک میکند و وقتی روی آن کلیک میشود، تابع لامبدا onClick که قبلاً تعریف شده است را اجرا میکند.
-
به طور خلاصه، این کد یک دکمه «ورود با گوگل» را در رابط کاربری Jetpack Compose تنظیم میکند. وقتی روی دکمه کلیک میشود، یک درخواست اعتبارنامه برای راهاندازی Credential Manager آماده میکند و به کاربر اجازه میدهد با حساب گوگل خود وارد شود.
حالا باید کلاس MainActivity را برای اجرای تابع ButtonUI() بهروزرسانی کنیم:
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//replace with your own web client ID from Google Cloud Console
val webClientId = "YOUR_CLIENT_ID_HERE"
setContent {
//ExampleTheme - this is derived from the name of the project not any added library
//e.g. if this project was named "Testing" it would be generated as TestingTheme
ExampleTheme {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background,
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
//This will trigger on launch
BottomSheet(webClientId)
//This requires the user to press the button
ButtonUI(webClientId)
}
}
}
}
}
}
حالا میتوانیم پروژهمان را ذخیره کنیم ( File > Save ) و آن را اجرا کنیم:
- دکمه اجرا را فشار دهید:

- وقتی برنامهها روی شبیهساز اجرا شدند، BottomSheet باید نمایش داده شود. برای بستن آن، روی قسمت بیرونی آن کلیک کنید.

- حالا باید دکمهای که ایجاد کردیم را در برنامه ببینید. روی آن کلیک کنید تا کادر محاورهای ورود به سیستم را ببینید.

- برای ورود به حساب کاربری خود کلیک کنید!
۸. نتیجهگیری
شما این آزمایشگاه کدنویسی را تمام کردید! برای اطلاعات بیشتر یا کمک در مورد ورود با گوگل در اندروید، به بخش سوالات متداول در زیر مراجعه کنید:
سوالات متداول
- استکاورفلو
- راهنمای عیبیابی مدیریت اعتبارنامه اندروید
- سوالات متداول در مورد مدیریت اعتبارنامه اندروید
- مرکز راهنمایی تأیید برنامه OAuth
کد کامل 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
}