1. Başlamadan önce
Bu codelab'de, Credential Manager'ı kullanarak Android'de Google ile oturum açma özelliğini nasıl uygulayacağınızı öğreneceksiniz.
Ön koşullar
- Android geliştirme için Kotlin kullanma konusunda temel bilgi sahibi olma
- Jetpack Compose hakkında temel bilgiler (Daha fazla bilgiye buradan ulaşabilirsiniz.)
Neler öğreneceksiniz?
- Google Cloud projesi oluşturma
- Google Cloud Console'da OAuth istemcileri oluşturma
- Alttaki sayfa akışını kullanarak Google ile oturum açma özelliğini uygulama
- Düğme akışını kullanarak Google ile oturum açma özelliğini uygulama
İhtiyacınız olanlar
- Android Studio (Buradan indirin)
- Android Studio sistem gereksinimlerini karşılayan bir bilgisayar
- Android Emülatör sistem gereksinimlerini karşılayan bir bilgisayar
- Java ve Java Development Kit (JDK)'nin yüklenmesi
2. Android Studio projesi oluşturma
Süre: 3:00 - 5:00
Başlamak için Android Studio'da yeni bir proje oluşturmamız gerekir:
- Android Studio'yu açın.
- Yeni Proje'yi tıklayın.
- Telefon ve Tablet ile Boş Etkinlik'i seçin.
- İleri'yi tıklayın.
- Şimdi projenin birkaç bölümünü ayarlayalım:
- Ad: Projenizin adı
- Paket adı: Proje adınıza göre otomatik olarak doldurulur.
- Kaydetme konumu: Bu, Android Studio'nun projelerinizi kaydettiği klasör olarak ayarlanmalıdır. Bunu istediğiniz zaman değiştirebilirsiniz.
- Minimum SDK: Bu, uygulamanızın üzerinde çalışacak şekilde oluşturulduğu en düşük Android SDK sürümüdür. Bu CodeLab'de API 36 (Baklava) kullanılacaktır.
- Son'u tıklayın.
- Android Studio, projeyi oluşturur ve temel uygulama için gerekli bağımlılıkları indirir. Bu işlem birkaç dakika sürebilir. Bunu görmek için derleme simgesini tıklamanız yeterlidir:
- Bu işlem tamamlandığında Android Studio aşağıdaki gibi görünmelidir:
3. Google Cloud projenizi oluşturma
Google Cloud projesi oluşturma
- Google Cloud Console'a gidin.
- Projenizi açın veya yeni bir proje oluşturun
- API'ler ve Hizmetler'i tıklayın.
- OAuth izin ekranı'na gidin.
- Devam etmek için Genel Bakış bölümündeki alanları doldurmanız gerekir. Bu bilgileri doldurmaya başlamak için Başlayın'ı tıklayın:
- Uygulama Adı: Bu uygulamanın adı. Android Studio'da projeyi oluştururken kullandığınız adla aynı olmalıdır.
- Kullanıcı Destek Ekibi E-postası: Bu bölümde, oturum açtığınız Google Hesabı ve yönettiğiniz Google Grupları gösterilir.
- Kitle:
- Yalnızca kuruluşunuzda kullanılan bir uygulama için dahili. Google Cloud projesiyle ilişkili bir kuruluşunuz yoksa bunu seçemezsiniz.
- Harici seçeneğini kullanacağız.
- İletişim Bilgileri: Bu alana, uygulamayla ilgili iletişim kurulmasını istediğiniz herhangi bir e-posta adresini girebilirsiniz.
- Google API Hizmetleri: Kullanıcı Verileri Politikası'nı inceleyin.
- Kullanıcı Verileri Politikası'nı inceleyip kabul ettikten sonra Oluştur'u tıklayın.
OAuth istemcilerini ayarlama
Google Cloud projesi oluşturduğumuza göre, istemci kimlikleriyle OAuth arka uç sunucusuna API çağrıları yapabilmek için bir web istemcisi ve Android istemcisi eklememiz gerekiyor.
Android Web İstemcisi için gerekenler:
- Uygulamanızın paket adı (ör. com.example.example)
- Uygulamanızın SHA-1 imzası
- SHA-1 imzası nedir?
- SHA-1 parmak izi, uygulamanızın imzalama anahtarından oluşturulan bir şifreleme karmasıdır. Bu, belirli uygulamanızın imzalama sertifikası için benzersiz bir tanımlayıcı görevi görür. Bunu uygulamanızın dijital "imzası" olarak düşünebilirsiniz.
- SHA-1 imzasına neden ihtiyaç duyuyoruz?
- SHA-1 parmak izi, OAuth 2.0 istemci kimliğinizi kullanarak yalnızca belirli imza anahtarınızla imzalanmış uygulamanızın erişim jetonları isteyebilmesini sağlar. Bu sayede diğer uygulamaların (aynı paket adına sahip olanlar bile) projenizin kaynaklarına ve kullanıcı verilerine erişmesi engellenir.
- Şöyle düşünün:
- Uygulamanızın imzalama anahtarı, uygulamanızın "kapısının" fiziksel anahtarı gibidir. Bu izin, uygulamanın iç işleyişine erişilmesine olanak tanır.
- SHA-1 parmak izi, fiziksel anahtarınıza bağlı benzersiz bir anahtar kartı kimliği gibidir. Bu, söz konusu anahtarı tanımlayan belirli bir koddur.
- OAuth 2.0 istemci kimliği, belirli bir Google kaynağına veya hizmetine (ör. Google ile oturum açma) giriş kodu gibidir.
- OAuth istemcisi kurulumu sırasında SHA-1 parmak izini sağladığınızda Google'a şu mesajı gönderirsiniz: "Bu erişim kodunu (istemci kimliği) yalnızca bu kimliğe (SHA-1) sahip anahtar kartı açabilir." Bu sayede, yalnızca uygulamanızın giriş koduna bağlı Google hizmetlerine erişebilmesi sağlanır."
- SHA-1 imzası nedir?
Web istemcisi için tek ihtiyacımız olan, istemciyi konsolda tanımlamak üzere kullanmak istediğiniz addır.
Android OAuth 2.0 istemcisi oluşturma
- İstemciler sayfasına gidin
- Müşteri Oluştur'u tıklayın.
- Uygulama türü olarak Android'i seçin.
- Uygulamanızın paket adını belirtmeniz gerekir.
- Android Studio'dan uygulamamızın SHA-1 imzasını alıp buraya kopyalayıp yapıştırmamız gerekir:
- Android Studio'ya gidip terminali açın.
- Bu komutu çalıştırın: Mac/Linux:
Windows:keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
Bu komut, bir anahtar deposundaki belirli bir girişin (takma ad) ayrıntılarını listelemek için tasarlanmıştır.keytool -list -v -keystore "C:\Users\USERNAME\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
-list
: Bu seçenek, keytool'a anahtar deposunun içeriğini listelemesini söyler.-v
: Bu seçenek, girişle ilgili daha ayrıntılı bilgiler sağlayan ayrıntılı çıkışı etkinleştirir.-keystore ~/.android/debug.keystore
: Bu, anahtar deposu dosyasının yolunu belirtir.-alias androiddebugkey
: Bu, incelemek istediğiniz anahtarın takma adını (giriş adı) belirtir.-storepass android
: Bu, anahtar deposu dosyasının şifresini sağlar.-keypass android
: Belirtilen takma adın özel anahtarının şifresini sağlar.
- SHA-1 imzası değerini kopyalayın:
- Google Cloud penceresine geri dönün ve SHA-1 imza değerini yapıştırın:
- Ekranınız artık buna benzer şekilde görünmelidir. Oluştur'u tıklayabilirsiniz:
Web OAuth 2.0 istemcisi oluşturma
- Web uygulaması istemci kimliği oluşturmak için Android istemcisi oluşturma bölümündeki 1-2. adımları tekrarlayın ve Uygulama Türü için Web Uygulaması'nı seçin.
- İstemciye bir ad verin (bu, OAuth istemcisi olacaktır):
- Oluştur'u tıklayın.
- Pop-up pencereden istemci kimliğini kopyalayın. Bu kimliğe daha sonra ihtiyacınız olacaktır.
OAuth istemcilerimizi ayarladığımıza göre artık Android Studio'ya dönüp Google ile oturum açma Android uygulamamızı oluşturabiliriz.
4. Android Sanal Cihazı Kurma
Uygulamanızı fiziksel bir Android cihaz olmadan hızlı bir şekilde test etmek için Android Studio'da uygulamanızı oluşturup hemen çalıştırabileceğiniz bir Android sanal cihazı oluşturmanız gerekir. Fiziksel bir Android cihazla test etmek isterseniz Android geliştirici belgelerindeki talimatları uygulayabilirsiniz.
Android sanal cihazı oluşturma
- Android Studio'da Cihaz Yöneticisi'ni açın
- + düğmesi > Sanal Cihaz Oluştur'u tıklayın.
- Buradan projeniz için ihtiyacınız olan tüm cihazları ekleyebilirsiniz. Bu Codelab'in amaçları doğrultusunda Medium Phone'u (Orta Boy Telefon) seçin ve Next'i (Sonraki) tıklayın.
- Artık cihaza benzersiz bir ad vererek, cihazın çalıştıracağı Android sürümünü seçerek ve daha birçok işlem yaparak cihazı projeniz için yapılandırabilirsiniz. API'nin API 36 "Baklava"; Android 16 olarak ayarlandığından emin olun ve Son'u tıklayın.
- Yeni cihazın, Cihaz Yöneticisi'nde gösterilmesi gerekir. Cihazın çalıştığını doğrulamak için
yeni oluşturduğunuz cihazın yanındaki
simgesini tıklayın.
- Cihazınız artık çalışıyor olmalıdır.
Android sanal cihazında oturum açma
Yeni oluşturduğunuz cihaz çalışıyor. Şimdi Google ile oturum açma özelliğini test ederken hata oluşmasını önlemek için cihazda bir Google Hesabı ile oturum açmamız gerekiyor.
- Ayarlar'a gidin:
- Sanal cihazda ekranın ortasını tıklayın ve yukarı kaydırın.
- Ayarlar uygulamasını bulup tıklayın.
- Ayarlar'da
Google'ı tıklayın.
- Oturum aç'ı tıklayın ve Google Hesabınızda oturum açmak için talimatları uygulayın
- Artık cihazda oturum açmış olmalısınız
Sanal Android cihazınız artık test için hazır.
5. Bağımlılık ekleme
Süre 5:00
OAuth API çağrıları yapmak için önce kimlik doğrulama istekleri göndermemize ve bu istekleri göndermek için Google kimliklerini kullanmamıza olanak tanıyan gerekli kitaplıkları entegre etmemiz gerekir:
- libs.googleid
- libs.play.services.auth
- Dosya > Proje Yapısı'na gidin:
- Ardından Dependencies > app > '+' > Library Dependency'ye (Bağımlılıklar > uygulama > "+" > Kitaplık Bağımlılığı) gidin.
- Şimdi kitaplıklarımızı eklememiz gerekiyor:
- Arama iletişim kutusuna googleid yazıp Ara'yı tıklayın.
- Yalnızca bir giriş olmalıdır. Devam edin, girişi ve mevcut en yüksek sürümü seçin (Bu Codelab'in hazırlandığı sırada 1.1.1 sürümü kullanılıyordu).
- Tamam'ı tıklayın.
- 1-3 arasındaki adımları tekrarlayın ancak "play-services-auth" ifadesini arayın ve Grup Kimliği olarak "com.google.android.gms" ve Yapı Adı olarak "play-services-auth" içeren satırı seçin.
- Tamam'ı tıklayın.
6. Alt sayfa akışı
Alt sayfa akışı, kullanıcıların Android'de Google Hesaplarını kullanarak uygulamanızda oturum açmalarını kolaylaştırmak için Credential Manager API'den yararlanır. Bu özellik, özellikle geri gelen kullanıcılar için hızlı ve kolay olacak şekilde tasarlanmıştır. Bu akış, uygulama başlatıldığında tetiklenmelidir.
Oturum açma isteğini oluşturma
- Başlamak için
MainActivity.kt
'danGreeting()
veGreetingPreview()
işlevlerini kaldırın. Bunlara ihtiyacımız olmayacak. - Şimdi, bu proje için ihtiyacımız olan paketlerin içe aktarıldığından emin olmamız gerekiyor. 3. satırdan itibaren mevcut ifadelerin sonuna aşağıdaki
import
ifadelerini ekleyin: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
- Ardından, alt sayfa isteğini oluşturmak için işlevimizi oluşturmamız gerekir. Bu kodu MainActivity sınıfının altına yapıştırın.
//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)
}
Bu kodun ne yaptığını inceleyelim:
fun BottomSheet(webClientId: String) {...}
: webClientId adlı bir dize bağımsız değişkeni alan BottomSheet adlı bir işlev oluşturur.
val context = LocalContext.current
: Geçerli Android bağlamını alır. Bu, kullanıcı arayüzü bileşenlerini başlatma gibi çeşitli işlemler için gereklidir.LaunchedEffect(Unit) { ... }
:LaunchedEffect
, composable'ın yaşam döngüsü içinde bir askıya alma işlevini (yürütmeyi duraklatıp devam ettirebilen bir işlev) çalıştırmanıza olanak tanıyan bir Jetpack Compose composable'ıdır. Anahtar olarak Unit, bu efektin yalnızca composable ilk başlatıldığında bir kez çalışacağı anlamına gelir.val googleIdOption: GetGoogleIdOption = ...
:GetGoogleIdOption
nesnesi oluşturur. Bu nesne, Google'dan istenen kimlik bilgisi türünü yapılandırır..Builder()
: Seçenekleri yapılandırmak için oluşturucu kalıbı kullanılır..setFilterByAuthorizedAccounts(true)
: Kullanıcının tüm Google Hesapları arasından seçim yapmasına veya yalnızca uygulamayı daha önce yetkilendirmiş olanlar arasından seçim yapmasına izin verilip verilmeyeceğini belirtir. Bu durumda, true olarak ayarlanır. Bu da varsa kullanıcının daha önce bu uygulamayla kullanılmak üzere yetkilendirdiği kimlik bilgilerinin kullanılarak istekte bulunulacağı anlamına gelir..setServerClientId(webClientId)
: Uygulamanızın arka ucunun benzersiz tanımlayıcısı olan sunucu istemci kimliğini ayarlar. Kimlik jetonu almak için bu gereklidir..setNonce(generateSecureRandomNonce())
: Tekrar oynatma saldırılarını önlemek ve kimlik jetonunun belirli bir istekle ilişkilendirilmesini sağlamak için tek kullanımlık rastgele bir değer ayarlar..build()
: Belirtilen yapılandırmaylaGetGoogleIdOption
nesnesini oluşturur.
val request: GetCredentialRequest = ...
:GetCredentialRequest
nesnesi oluşturur. Bu nesne, kimlik bilgisi isteğinin tamamını kapsar..Builder()
: İsteği yapılandırmak için oluşturucu kalıbını başlatır..addCredentialOption(googleIdOption)
: İsteğe googleIdOption'ı ekleyerek Google kimliği jetonu isteğinde bulunmak istediğimizi belirtir..build()
:GetCredentialRequest
nesnesini oluşturur.
val e = signIn(request, context)
: Bu, oluşturulan istek ve mevcut bağlamla kullanıcının oturumunu açmaya çalışır. signIn işlevinin sonucu e değişkeninde saklanır. Bu değişken, başarılı sonucu veya bir istisnayı içerir.if (e is NoCredentialException) { ... }
: Bu, koşullu bir kontrol. signIn işlevi NoCredentialException ile başarısız olursa daha önce yetkilendirilmiş hesap olmadığı anlamına gelir.val googleIdOptionFalse: GetGoogleIdOption = ...
: ÖncekisignIn
başarısız olursa bu bölüm yeni birGetGoogleIdOption
oluşturur..setFilterByAuthorizedAccounts(false)
: Bu, ilk seçenekteki kritik farktır. Bu ayar, yetkili hesapların filtrelenmesini devre dışı bırakır. Yani cihazdaki herhangi bir Google Hesabı ile oturum açılabilir.val requestFalse: GetCredentialRequest = ...
:googleIdOptionFalse
ile yeni birGetCredentialRequest
oluşturulur.signIn(requestFalse, context)
: Bu, kullanıcının herhangi bir hesabın kullanılmasına izin veren yeni istekle oturum açmasını denemeye çalışır.
Bu kod, sağlanan yapılandırmaları kullanarak kullanıcı için bir Google kimlik jetonu almak üzere Kimlik Bilgisi Yöneticisi API'sine bir istek hazırlar. Daha sonra GetCredentialRequest, kimlik bilgisi yöneticisi kullanıcı arayüzünü başlatmak için kullanılabilir. Kullanıcı, bu arayüzde Google Hesabını seçip gerekli izinleri verebilir.
fun generateSecureRandomNonce(byteLength: Int = 32): String
: Bu, generateSecureRandomNonce
adlı bir işlevi tanımlar. Bayt cinsinden nonce'ın istenen uzunluğunu belirten bir tam sayı bağımsız değişkeni olan byteLength'i (varsayılan değeri 32) kabul eder. Rastgele baytların Base64 kodlu gösterimi olan bir dize döndürür.
val randomBytes = ByteArray(byteLength)
: Rastgele baytları tutmak için belirtilen byteLength değerinde bir bayt dizisi oluşturur.SecureRandom.getInstanceStrong().nextBytes(randomBytes)
:SecureRandom.getInstanceStrong()
: Bu, kriptografik olarak güçlü bir rastgele sayı oluşturma aracı elde eder. Bu, oluşturulan sayıların gerçekten rastgele olmasını ve tahmin edilememesini sağladığı için güvenlik açısından çok önemlidir. Sistemdeki mevcut en güçlü entropi kaynağını kullanır..nextBytes(randomBytes)
: Bu, randomBytes dizisini SecureRandom örneği tarafından oluşturulan rastgele baytlarla doldurur.
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
:Base64.getUrlEncoder()
: Bu, URL için güvenli bir alfabe (Base64'te + ve / yerine - ve _ kullanılarak) kullanan bir Base64 kodlayıcı alır. Bu önemlidir, çünkü sonuçta elde edilen dizenin daha fazla kodlamaya gerek kalmadan URL'lerde güvenli bir şekilde kullanılmasını sağlar..withoutPadding()
: Bu, Base64 olarak kodlanmış dizedeki tüm dolgu karakterlerini kaldırır. Bu, genellikle nonce'ı biraz daha kısa ve kompakt hale getirmek için istenir..encodeToString(randomBytes)
: Bu işlev, randomBytes'ı Base64 dizesi olarak kodlar ve döndürür.
Özetlemek gerekirse bu işlev, belirtilen uzunlukta kriptografik olarak güçlü bir rastgele nonce oluşturur, bunu URL için güvenli Base64 kullanarak kodlar ve sonuçta elde edilen dizeyi döndürür. Bu, güvenlikle ilgili hassas bağlamlarda kullanılması güvenli olan tek kullanımlık sayıları oluşturmak için kullanılan standart bir uygulamadır.
Oturum açma isteğinde bulunma
Oturum açma isteğimizi oluşturabildiğimize göre, oturum açmak için Kimlik Bilgisi Yöneticisi'ni kullanabiliriz. Bunu yapmak için, kimlik bilgisi yöneticisini kullanarak oturum açma isteklerinin iletilmesini sağlayan ve karşılaşabileceğimiz yaygın istisnaları işleyen bir işlev oluşturmamız gerekir.
Bunu yapmak için bu işlevi BottomSheet()
işlevinin altına yapıştırabilirsiniz.
//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
}
Şimdi kodun burada ne yaptığını inceleyelim:
suspend fun signIn(request: GetCredentialRequest, context: Context): Exception?
: Bu, signIn adlı bir askıya alma işlevini tanımlar. Bu, ana iş parçacığı engellenmeden duraklatılabileceği ve devam ettirilebileceği anlamına gelir.Oturum açma işlemi başarılı olursa null, başarısız olursa belirli istisna döndürür.Exception?
İki parametre alır:
request
: Alınacak kimlik bilgisi türünün yapılandırmasını içeren birGetCredentialRequest
nesnesi (ör. Google kimliği).context
: Sistemle etkileşim kurmak için gereken Android bağlamı.
İşlevin gövdesi için:
val credentialManager = CredentialManager.create(context)
: Credential Manager API ile etkileşim kurmak için kullanılan ana arayüz olan CredentialManager'ın bir örneğini oluşturur. Uygulama, oturum açma akışını bu şekilde başlatır.val failureMessage = "Sign in failed!"
: Oturum açma işlemi başarısız olduğunda durum mesajında gösterilecek bir dize (failureMessage) tanımlar.var e: Exception? = null
: Bu satır, işlem sırasında oluşabilecek istisnaları depolamak için e değişkenini başlatır ve başlangıç değeri olarak null'ı kullanır.delay(250)
: 250 milisaniyelik bir gecikme oluşturur. Bu, özellikle BottomSheet akışı kullanılırken uygulamanın başlatılmasıyla birlikte NoCredentialException'ın hemen oluşturulabileceği olası bir soruna yönelik geçici bir çözümdür. Bu sayede sistem, kimlik bilgisi yöneticisini başlatmak için zaman kazanır.try { ... } catch (e: Exception) { ... }
:Güçlü hata işleme için try-catch bloğu kullanılır. Bu sayede, oturum açma işlemi sırasında herhangi bir hata oluşursa uygulamanın kilitlenmesi önlenir ve istisna sorunsuz bir şekilde ele alınabilir.val result = credentialManager.getCredential(request = request, context = context)
: Credential Manager API'ye yapılan gerçek çağrı burada gerçekleşir ve kimlik bilgisi alma süreci başlatılır. İsteği ve bağlamı giriş olarak alır ve kullanıcıya bir kimlik bilgisi seçmesi için kullanıcı arayüzü sunar. İşlem başarılı olursa seçilen kimlik bilgisini içeren bir sonuç döndürülür. Bu işlemin sonucu olanGetCredentialResponse
,result
değişkeninde saklanır.Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
:Oturum açma işleminin başarılı olduğunu belirten kısa bir durum mesajı gösterir.Log.i(TAG, "Sign in Successful!")
: Logcat'e eğlenceli ve başarılı bir mesaj kaydeder.catch (e: GetCredentialException)
:GetCredentialException
türündeki istisnaları işler. Bu, kimlik bilgisi getirme işlemi sırasında oluşabilecek çeşitli özel istisnalar için bir üst sınıftır.catch (e: GoogleIdTokenParsingException)
: Google kimlik jetonu ayrıştırılırken hata oluştuğunda ortaya çıkan istisnaları işler.catch (e: NoCredentialException)
: Kullanıcı için kimlik bilgisi olmadığında (ör. kimlik bilgisi kaydetmemiş veya Google Hesabı yoksa) oluşturulanNoCredentialException
öğesini işler.- Bu işlev,
e
içinde depolanan istisnayı döndürür.NoCredentialException
Bu sayede, kimlik bilgisi yoksa arayanın özel durumu işlemesine olanak tanır.
- Bu işlev,
catch (e: GetCredentialCustomException)
: Kimlik bilgisi sağlayıcı tarafından oluşturulabilecek özel istisnaları işler.catch (e: GetCredentialCancellationException)
: Kullanıcı oturum açma işlemini iptal ettiğinde oluşturulanGetCredentialCancellationException
öğesini işler.Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
: failureMessage kullanılarak oturum açma işleminin başarısız olduğunu belirten bir durum mesajı görüntüler.Log.e(TAG, "", e)
: Hata için kullanılan Log.e ile istisnayı Android logcat'e kaydeder. Bu, hata ayıklamaya yardımcı olmak için istisnanın yığın izini içerir. Ayrıca eğlence için kızgın surat emojisi de yer alıyor.
return e
: İşlev, yakalanan bir istisna varsa istisnayı, oturum açma işlemi başarılıysa null değerini döndürür.
Özetle bu kod, Credential Manager API'yi kullanarak kullanıcı oturum açma işlemlerini yönetmenin, eşzamansız işlemleri yönetmenin, olası hataları ele almanın ve hata işlemeye biraz mizah katarak kullanıcıya pop-up mesajlar ve günlükler aracılığıyla geri bildirim sağlamanın bir yolunu sunar.
Uygulamada alt sayfa akışını uygulayın
Şimdi aşağıdaki kodu ve daha önce Google Cloud Console'dan kopyaladığımız web uygulaması istemci kimliğimizi kullanarak MainActivity
sınıfımızda BottomSheet akışını tetikleyecek bir çağrı ayarlayabiliriz:
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)
}
}
}
}
}
Artık projemizi kaydedebilir (Dosya > Kaydet) ve çalıştırabiliriz:
- Çalıştır düğmesine basın:
- Uygulamanız emülatörde çalıştıktan sonra oturum açma BottomSheet'inin açıldığını görmeniz gerekir. Oturum açmayı test etmek için Devam'ı tıklayın.
- Oturum açma işleminin başarılı olduğunu gösteren bir Toast mesajı görmeniz gerekir.
7. Düğme akışı
Google ile oturum açma için düğme akışı, kullanıcıların mevcut Google Hesaplarını kullanarak Android uygulamanıza kaydolmasını veya giriş yapmasını kolaylaştırır. Kullanıcılar, alt sayfayı kapatırsa veya oturum açma ya da kaydolma için Google Hesaplarını açıkça kullanmayı tercih ederse bu düğmeye dokunur. Geliştiriciler için daha sorunsuz bir ilk katılım süreci ve kayıt sırasında daha az sorun anlamına gelir.
Bu işlem, kullanıma hazır bir Jetpack Compose düğmesiyle yapılabilir ancak Google ile Oturum Açma Markalama Kuralları sayfasından önceden onaylanmış bir marka simgesi kullanacağız.
Projeye marka simgesi ekleme
- Önceden onaylanmış marka simgelerinin ZIP dosyasını buradan indirin.
- İndirilenler klasöründeki signin-assest.zip dosyasını açın (bu işlem, bilgisayarınızın işletim sistemine göre değişiklik gösterir). Artık signin-assets klasörünü açıp mevcut simgelere göz atabilirsiniz. Bu Codelab'de
signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png
kullanacağız. - Dosyayı kopyalama
- drawable klasörünü sağ tıklayıp Yapıştır'ı tıklayarak (görmek için res klasörünü genişletmeniz gerekebilir) Android Studio'da res > drawable altında projeye yapıştırın.
- Dosyayı yeniden adlandırmanızı ve ekleneceği dizini onaylamanızı isteyen bir iletişim kutusu gösterilir. Öğeyi siwg_button.png olarak yeniden adlandırın ve Tamam'ı tıklayın.
Düğme akışı kodu
Bu kod, BottomSheet()
için kullanılan signIn()
işlevini kullanır ancak bu akış, oturum açma seçeneklerini göstermek için cihazda depolanan kimlik bilgilerinden ve geçiş anahtarlarından yararlanmadığından GetGoogleIdOption
yerine GetSignInWithGoogleOption
işlevini kullanır. Aşağıdaki BottomSheet()
işlevinin altına yapıştırabileceğiniz kod:
@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)
)
}
Kodun ne yaptığını anlamak için:
fun ButtonUI(webClientId: String)
: Bu, bağımsız değişken olarak webClientId
(Google Cloud projenizin istemci kimliği) kabul eden ButtonUI
adlı bir işlevi bildirir.
val context = LocalContext.current
: Geçerli Android bağlamını alır. Bu, kullanıcı arayüzü bileşenlerini başlatma gibi çeşitli işlemler için gereklidir.
val coroutineScope = rememberCoroutineScope()
: Eş yordam kapsamı oluşturur. Bu, eşzamansız görevleri yönetmek için kullanılır ve kodun ana iş parçacığını engellemeden çalışmasına olanak tanır. rememberCoroutineScope
(), composable'ın yaşam döngüsüne bağlı bir kapsam sağlayan Jetpack Compose'daki bir composable işlevdir.
val onClick: () -> Unit = { ... }
: Bu, düğme tıklandığında yürütülecek bir lambda işlevi oluşturur. Lambda işlevi:
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(serverClientId = webClientId).setNonce(generateSecureRandomNonce()).build()
: Bu bölüm,GetSignInWithGoogleOption
nesnesi oluşturur. Bu nesne, "Google ile Oturum Açma" sürecinin parametrelerini belirtmek için kullanılır.webClientId
ve nonce (güvenlik için kullanılan rastgele bir dize) gerektirir.val request: GetCredentialRequest = GetCredentialRequest.Builder().addCredentialOption(signInWithGoogleOption).build()
: Bu, birGetCredentialRequest
nesnesi oluşturur. Bu istek, Kimlik Bilgileri Yöneticisi kullanılarak kullanıcı kimlik bilgisini almak için kullanılır.GetCredentialRequest
, "Google ile oturum aç" kimlik bilgisi isteğinde bulunmak için daha önce oluşturulanGetSignInWithGoogleOption
öğesini seçenek olarak ekler.
coroutineScope.launch { ... }
: Eşzamansız işlemleri (eş yordamları kullanarak) yönetmek içinCoroutineScope
.signIn(request, context)
: Önceden tanımladığımızsignIn
() işlevini çağırır.
Image(...)
: Bu, resmi yükleyen painterResource
kullanılarak bir resim oluşturur R.drawable.siwg_button
Modifier.fillMaxSize().clickable(enabled = true, onClick = onClick)
:fillMaxSize()
: Resmin, mevcut alanı doldurmasını sağlar.clickable(enabled = true, onClick = onClick)
: Resmi tıklanabilir hale getirir ve tıklandığında daha önce tanımlanmış onClick lambda işlevini yürütür.
Özetle bu kod, Jetpack Compose kullanıcı arayüzünde "Google ile oturum aç" düğmesi oluşturur. Düğme tıklandığında, Kimlik Bilgisi Yöneticisi'ni başlatmak ve kullanıcının Google Hesabı ile oturum açmasına izin vermek için bir kimlik bilgisi isteği hazırlanır.
Şimdi ButtonUI()
işlevimizi çalıştırmak için MainActivity sınıfını güncellememiz gerekiyor:
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)
}
}
}
}
}
}
Artık projemizi kaydedebilir (Dosya > Kaydet) ve çalıştırabiliriz:
- Çalıştır düğmesine basın:
- Uygulama emülatörde çalıştırıldıktan sonra BottomSheet gösterilmelidir. Kapatmak için dışını tıklayın.
- Oluşturduğumuz düğme artık uygulamada gösteriliyor olmalıdır. Oturum açma iletişim kutusunu görmek için düğmeyi tıklayın.
- Oturum açmak için hesabınızı tıklayın.
8. Sonuç
Bu codelab'i tamamladınız. Android'de Google ile oturum açma hakkında daha fazla bilgi veya yardım için aşağıdaki Sık Sorulan Sorular bölümüne bakın:
Sık Sorulan Sorular
- Stackoverflow
- Android Credential Manager Sorun Giderme Kılavuzu
- Android Kimlik Bilgisi Yöneticisi hakkında SSS
- OAuth Uygulama Doğrulaması Yardım Merkezi
MainActivity.kt dosyasının tam kodu
Referans için MainActivity.kt dosyasının tam kodu aşağıda verilmiştir:
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
}