Android uygulamanızda Google ile oturum açma özelliğini nasıl uygulayacağınızı öğrenin

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

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:

  1. Android Studio'yu açın.
  2. Yeni Proje'yi tıklayın.Android Studio'ya Hoş Geldiniz
  3. Telefon ve Tablet ile Boş Etkinlik'i seçin.Android Studio Projesi
  4. İleri'yi tıklayın.
  5. Ş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.
    Android Studio'da Proje Oluşturma
  6. Son'u tıklayın.
  7. 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:Android Studio Proje Derleme
  8. Bu işlem tamamlandığında Android Studio aşağıdaki gibi görünmelidir:Android Studio Projesi Oluşturuldu

3. Google Cloud projenizi oluşturma

Google Cloud projesi oluşturma

  1. Google Cloud Console'a gidin.
  2. Projenizi açın veya yeni bir proje oluşturunGCP'de yeni proje oluşturmaGCP create new project 2GCP create new project 3
  3. API'ler ve Hizmetler'i tıklayın.GCP API'leri ve Hizmetleri
  4. OAuth izin ekranı'na gidin.GCP OAuth izin ekranı
  5. 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:GCP'yi kullanmaya başlama düğmesi
    • 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.
    GCP Uygulama Bilgileri
    • 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.
    GCP Kitlesi
    • İletişim Bilgileri: Bu alana, uygulamayla ilgili iletişim kurulmasını istediğiniz herhangi bir e-posta adresini girebilirsiniz.
    GCP İletişim Bilgileri
    • Google API Hizmetleri: Kullanıcı Verileri Politikası'nı inceleyin.
  6. Kullanıcı Verileri Politikası'nı inceleyip kabul ettikten sonra Oluştur'u tıklayın.GCP Oluşturma

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."

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

  1. İstemciler sayfasına gidinGCP Müşterileri
  2. Müşteri Oluştur'u tıklayın.GCP'de istemci oluşturma
  3. Uygulama türü olarak Android'i seçin.
  4. Uygulamanızın paket adını belirtmeniz gerekir.
  5. Android Studio'dan uygulamamızın SHA-1 imzasını alıp buraya kopyalayıp yapıştırmamız gerekir:
    1. Android Studio'ya gidip terminali açın.
    2. Bu komutu çalıştırın: 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
      
      Bu komut, bir anahtar deposundaki belirli bir girişin (takma ad) ayrıntılarını listelemek için tasarlanmıştır.
      • -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.
    3. SHA-1 imzası değerini kopyalayın:
    SHA İmzası
    1. Google Cloud penceresine geri dönün ve SHA-1 imza değerini yapıştırın:
  6. Ekranınız artık buna benzer şekilde görünmelidir. Oluştur'u tıklayabilirsiniz:Android İstemci AyrıntılarıAndroid İstemcisi

Web OAuth 2.0 istemcisi oluşturma

  1. 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.
  2. İstemciye bir ad verin (bu, OAuth istemcisi olacaktır): Web İstemcisi Ayrıntıları
  3. Oluştur'u tıklayın.Web istemcisi
  4. Pop-up pencereden istemci kimliğini kopyalayın. Bu kimliğe daha sonra ihtiyacınız olacaktır.İstemci kimliğini kopyalama

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

  1. Android Studio'da Cihaz Yöneticisi'ni açınCihaz Yöneticisi
  2. + düğmesi > Sanal Cihaz Oluştur'u tıklayın.Sanal Cihaz Oluşturma
  3. 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.Medium Phone
  4. 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.Sanal Cihazı Yapılandırma
  5. Yeni cihazın, Cihaz Yöneticisi'nde gösterilmesi gerekir. Cihazın çalıştığını doğrulamak için Cihazı çalıştırma yeni oluşturduğunuz cihazın yanındaki Cihazı çalıştırma 2 simgesini tıklayın.
  6. Cihazınız artık çalışıyor olmalıdır.Çalışan Cihaz

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.

  1. Ayarlar'a gidin:
    1. Sanal cihazda ekranın ortasını tıklayın ve yukarı kaydırın.
    Tıklama ve Kaydırma
    1. Ayarlar uygulamasını bulup tıklayın.
    Ayarlar uygulaması
  2. Ayarlar'da Google Hizmetleri ve Tercihler Google'ı tıklayın.
  3. Oturum aç'ı tıklayın ve Google Hesabınızda oturum açmak için talimatları uygulayınCihazda oturum açma
  1. Artık cihazda oturum açmış olmalısınızCihazda oturum açıldı

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
  1. Dosya > Proje Yapısı'na gidin:Proje Yapısı
  2. Ardından Dependencies > app > '+' > Library Dependency'ye (Bağımlılıklar > uygulama > "+" > Kitaplık Bağımlılığı) gidin.Bağımlılıklar
  3. Şimdi kitaplıklarımızı eklememiz gerekiyor:
    1. Arama iletişim kutusuna googleid yazıp Ara'yı tıklayın.
    2. 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).
    3. Tamam'ı tıklayın.Google ID Package
    4. 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.Play Hizmetleri Kimlik Doğrulaması
  4. Tamam'ı tıklayın.Tamamlanan Bağımlılıklar

6. Alt sayfa akışı

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

  1. Başlamak için MainActivity.kt'dan Greeting() ve GreetingPreview() işlevlerini kaldırın. Bunlara ihtiyacımız olmayacak.
  2. Ş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
    
  3. 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ırmayla GetGoogleIdOption 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 = ...: Önceki signIn başarısız olursa bu bölüm yeni bir GetGoogleIdOption 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 bir GetCredentialRequest 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 bir GetCredentialRequest 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 olan GetCredentialResponse, 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şturulan NoCredentialException öğ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.
    • 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şturulan GetCredentialCancellationException öğ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:

  1. Çalıştır düğmesine basın:Projeyi Çalıştır
  2. 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.alt sayfa
  3. Oturum açma işleminin başarılı olduğunu gösteren bir Toast mesajı görmeniz gerekir.Alt Sayfa Başarısı

7. Düğme akışı

Düğme akışı GIF&#39;i

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

  1. Önceden onaylanmış marka simgelerinin ZIP dosyasını buradan indirin.
  2. İ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.
  3. Dosyayı kopyalama
  4. 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.Çekilebilir
  5. 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 Ekleme

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, bir GetCredentialRequest 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şturulan GetSignInWithGoogleOption öğesini seçenek olarak ekler.
  • coroutineScope.launch { ... }: Eşzamansız işlemleri (eş yordamları kullanarak) yönetmek için CoroutineScope.
    • signIn(request, context): Önceden tanımladığımız signIn() 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:

  1. Çalıştır düğmesine basın:Projeyi Çalıştır
  2. Uygulama emülatörde çalıştırıldıktan sonra BottomSheet gösterilmelidir. Kapatmak için dışını tıklayın.Buraya dokunun
  3. 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çma iletişim kutusu
  4. 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

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
}