Pelajari cara menerapkan Login dengan Google di aplikasi Android Anda

1. Sebelum memulai

Dalam codelab ini, Anda akan mempelajari cara menerapkan Login dengan Google di Android menggunakan Credential Manager.

Prasyarat

  • Pemahaman dasar tentang penggunaan Kotlin untuk pengembangan Android
  • Pemahaman dasar tentang Jetpack Compose (Info selengkapnya dapat ditemukan di sini)

Yang akan Anda pelajari

  • Cara membuat Project Google Cloud
  • Cara membuat klien OAuth di Konsol Google Cloud
  • Cara menerapkan Login dengan Google menggunakan alur Sheet Bawah
  • Cara menerapkan Login dengan Google menggunakan alur Tombol

Yang Anda perlukan

2. Membuat project Android Studio

Durasi 3:00 - 5:00

Untuk memulai, kita perlu membuat project baru di Android Studio:

  1. Membuka Android Studio
  2. Klik New ProjectSelamat Datang di Android Studio
  3. Pilih Phone and Tablet dan Empty ActivityProject Android Studio
  4. Klik Berikutnya.
  5. Sekarang saatnya menyiapkan beberapa bagian project:
    • Nama: ini adalah nama project Anda
    • Package name: kolom ini akan otomatis terisi berdasarkan nama project Anda
    • Save location: secara default, ini akan menjadi folder tempat Android Studio menyimpan project Anda. Anda dapat mengubahnya ke mana pun Anda inginkan.
    • Minimum SDK: Ini adalah versi terendah Android SDK yang digunakan untuk membangun aplikasi Anda agar dapat berjalan. Dalam CodeLab ini, kita akan menggunakan API 36 (Baklava)
    Project Penyiapan Android Studio
  6. Klik Finish
  7. Android Studio akan membuat project dan mendownload dependensi yang diperlukan untuk aplikasi dasar. Proses ini dapat memakan waktu beberapa menit. Untuk melihatnya, cukup klik ikon build:Membangun Project Android Studio
  8. Setelah selesai, Android Studio akan terlihat seperti ini:Project Android Studio yang Dibuat

3. Menyiapkan project Google Cloud

Membuat project Google Cloud

  1. Buka Konsol Google Cloud
  2. Buka project Anda atau buat project baruGCP membuat project baruGCP membuat project baru 2GCP membuat project baru 3
  3. Klik APIs & ServicesAPI & Layanan GCP
  4. Buka OAuth consent screenLayar izin OAuth GCP
  5. Anda harus mengisi kolom di Ringkasan untuk melanjutkan. Klik Mulai untuk mulai mengisi informasi ini:Tombol Mulai GCP
    • Nama Aplikasi: Nama aplikasi ini, yang harus sama dengan yang Anda gunakan saat membuat project di Android Studio
    • Email Dukungan Pengguna: Bagian ini akan menampilkan Akun Google yang Anda gunakan untuk login dan semua Grup Google yang Anda kelola
    Info Aplikasi GCP
    • Audiens:
      • Internal untuk aplikasi yang hanya digunakan dalam organisasi Anda. Jika Anda tidak memiliki organisasi yang terkait dengan Project Google Cloud, Anda tidak akan dapat memilihnya.
      • Eksternal adalah yang akan kita gunakan.
    Audiens GCP
    • Info Kontak: Dapat berupa email apa pun yang Anda inginkan sebagai kontak untuk aplikasi
    Info Kontak GCP
    • Tinjau Layanan Google API: Kebijakan Data Pengguna.
  6. Setelah Anda meninjau dan menyetujui Kebijakan Data Pengguna, klik BuatPembuatan GCP

Menyiapkan klien OAuth

Setelah menyiapkan Project Google Cloud, kita perlu menambahkan Klien Web dan Klien Android agar dapat melakukan panggilan API ke server backend OAuth dengan client ID-nya.

Untuk Klien Web Android, Anda memerlukan:

  • Nama paket aplikasi Anda (mis. com.example.example)
  • Tanda tangan SHA-1 aplikasi Anda
    • Apa yang dimaksud dengan Tanda Tangan SHA-1?
      • Sidik jari SHA-1 adalah hash kriptografi yang dibuat dari kunci penandatanganan aplikasi Anda. ID ini berfungsi sebagai ID unik untuk sertifikat penandatanganan aplikasi spesifik Anda. Anggap saja seperti "tanda tangan" digital untuk aplikasi Anda.
    • Mengapa kami memerlukan tanda tangan SHA-1?
      • Sidik jari SHA-1 memastikan bahwa hanya aplikasi Anda, yang ditandatangani dengan kunci penandatanganan khusus Anda, yang dapat meminta token akses menggunakan ID klien OAuth 2.0 Anda, sehingga mencegah aplikasi lain (bahkan yang memiliki nama paket yang sama) mengakses resource dan data pengguna project Anda.
      • Anggap saja seperti ini:
        • Kunci penandatanganan aplikasi Anda seperti kunci fisik untuk "pintu" aplikasi Anda. Hal ini memungkinkan akses ke cara kerja internal aplikasi.
        • Sidik jari SHA-1 seperti ID kartu kunci unik yang ditautkan ke kunci fisik Anda. Kode ini adalah kode khusus yang mengidentifikasi kunci tertentu tersebut.
        • ID klien OAuth 2.0 seperti kode masuk ke layanan atau resource Google tertentu (misalnya, Login dengan Google).
        • Saat Anda memberikan sidik jari SHA-1 selama penyiapan klien OAuth, pada dasarnya Anda memberi tahu Google: "Hanya kartu kunci dengan ID (SHA-1) tertentu ini yang dapat membuka kode akses (ID klien) ini." Hal ini memastikan hanya aplikasi Anda yang dapat mengakses layanan Google yang ditautkan ke kode entri tersebut."

Untuk Klien Web, yang kami butuhkan hanyalah nama yang ingin Anda gunakan untuk mengidentifikasi klien di konsol.

Buat klien OAuth 2.0 Android

  1. Buka halaman KlienKlien GCP
  2. Klik Buat KlienMembuat Klien GCP
  3. Pilih Android untuk Jenis aplikasi
  4. Anda harus menentukan nama paket aplikasi
  5. Dari Android Studio, kita perlu mendapatkan tanda tangan SHA-1 aplikasi dan menyalin/menempelkannya di sini:
    1. Buka Android Studio dan buka terminal
    2. Jalankan perintah ini:
      keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
      
      Perintah ini dirancang untuk mencantumkan detail entri (alias) tertentu dalam keystore.
      • -list: Opsi ini memberi tahu keytool untuk mencantumkan konten keystore.
      • -v: Opsi ini mengaktifkan output panjang, yang memberikan informasi lebih mendetail tentang entri.
      • -keystore ~/.android/debug.keystore: Ini menentukan jalur ke file keystore.
      • -alias androiddebugkey: Ini menentukan alias (nama entri) kunci yang ingin Anda periksa.
      • -storepass android: Ini memberikan sandi untuk file keystore.
      • -keypass android: Ini memberikan sandi untuk kunci pribadi alias yang ditentukan.
    3. Salin nilai tanda tangan SHA-1:
    Tanda Tangan SHA
    1. Kembali ke jendela Google Cloud dan tempel nilai tanda tangan SHA-1:
  6. Layar Anda akan terlihat seperti ini, dan Anda dapat mengklik Buat:Detail Klien AndroidKlien Android

Buat klien OAuth 2.0 web

  1. Untuk membuat client ID Aplikasi web, ulangi langkah 1-2 dari bagian Buat Klien Android dan pilih Aplikasi Web untuk Jenis Aplikasi
  2. Beri nama klien (ini akan menjadi Klien OAuth): Detail Klien Web
  3. Klik BuatKlien Web
  4. Lanjutkan dan salin ID klien dari jendela pop-up, Anda akan memerlukannya nantiSalin Client ID

Setelah Klien OAuth disiapkan, kita dapat kembali ke Android Studio untuk membuat aplikasi Android Login dengan Google.

4. Menyiapkan Perangkat Virtual Android

Untuk pengujian cepat aplikasi tanpa perangkat Android fisik, Anda harus membuat Perangkat Virtual Android yang dapat Anda bangun dan langsung jalankan aplikasi dari Android Studio. Jika Anda ingin melakukan pengujian dengan perangkat Android fisik, Anda dapat mengikuti petunjuk dari dokumentasi developer Android

Membuat Perangkat Virtual Android

  1. Di Android Studio, buka Pengelola PerangkatPengelola Perangkat
  2. Klik tombol + > Buat Perangkat VirtualMembuat Perangkat Virtual
  3. Dari sini, Anda dapat menambahkan perangkat apa pun yang Anda butuhkan untuk project Anda. Untuk tujuan Codelab ini, pilih Medium Phone, lalu klik NextPonsel Sedang
  4. Sekarang Anda dapat mengonfigurasi perangkat untuk project dengan memberinya nama unik, memilih versi Android yang akan dijalankan perangkat, dan lainnya. Pastikan API disetel ke API 36 "Baklava"; Android 16, lalu klik SelesaiMengonfigurasi Perangkat Virtual
  5. Anda akan melihat perangkat baru muncul di Pengelola Perangkat. Untuk memverifikasi bahwa perangkat berjalan, lanjutkan dan klik Menjalankan Perangkat di samping perangkat yang baru saja Anda buatJalankan Perangkat 2
  6. Perangkat akan berjalan sekarang.Menjalankan Perangkat

Login ke Perangkat Virtual Android

Perangkat yang baru saja Anda buat berfungsi. Sekarang, untuk mencegah error saat menguji Login dengan Google, kita harus login ke perangkat dengan Akun Google.

  1. Buka Setelan:
    1. Klik bagian tengah layar di perangkat virtual dan geser ke atas
    Klik dan Geser
    1. Cari aplikasi Setelan, lalu klik
    Aplikasi Setelan
  2. Klik Google di SetelanLayanan & Preferensi Google
  3. Klik Login dan ikuti petunjuk untuk login ke Akun Google AndaLogin Perangkat
  1. Sekarang Anda seharusnya sudah login di perangkatPerangkat Login

Perangkat Android Virtual Anda kini siap untuk pengujian.

5. Menambahkan dependensi

Durasi 5:00

Untuk melakukan panggilan API OAuth, kita harus mengintegrasikan library yang diperlukan terlebih dahulu agar kita dapat membuat permintaan autentikasi dan menggunakan ID Google untuk membuat permintaan tersebut:

  • libs.googleid
  • libs.play.services.auth
  1. Buka File > Project Structure:Struktur Project
  2. Kemudian, buka Dependencies > app > '+' > Library DependencyDependensi
  3. Sekarang kita perlu menambahkan library:
    1. Di dialog penelusuran, ketik googleid, lalu klik Telusuri
    2. Hanya akan ada satu entri, lanjutkan, pilih entri tersebut dan versi tertinggi yang tersedia (Pada saat Codelab ini dibuat, versinya adalah 1.1.1)
    3. Klik OKPaket ID Google
    4. Ulangi langkah 1-3, tetapi telusuri "play-services-auth", lalu pilih baris dengan "com.google.android.gms" sebagai ID Grup dan "play-services-auth" sebagai Nama ArtefakAutentikasi Layanan Play
  4. Klik OKDependensi Selesai

6. Alur sheet bawah

Alur Sheet Bawah

Alur sheet bawah memanfaatkan Credential Manager API untuk memberikan cara yang lebih mudah bagi pengguna untuk login ke aplikasi Anda menggunakan Akun Google mereka di Android. Fitur ini dirancang agar cepat dan nyaman, terutama bagi pengguna yang kembali. Alur ini harus dipicu saat peluncuran aplikasi.

Buat permintaan login

  1. Untuk memulai, hapus fungsi Greeting() dan GreetingPreview() dari MainActivity.kt, karena kita tidak akan membutuhkannya.
  2. Sekarang kita perlu memastikan bahwa paket yang kita butuhkan diimpor untuk project ini. Lanjutkan dan tambahkan pernyataan import berikut setelah pernyataan yang ada, dimulai dari baris 3::
    import android.content.ContentValues.TAG
    import android.content.Context
    import android.credentials.GetCredentialException
    import android.os.Build
    import android.util.Log
    import android.widget.Toast
    import androidx.annotation.RequiresApi
    import androidx.compose.foundation.clickable
    import androidx.compose.foundation.Image
    import androidx.compose.foundation.layout.Arrangement
    import androidx.compose.foundation.layout.Column
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Surface
    import androidx.compose.runtime.rememberCoroutineScope
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.platform.LocalContext
    import androidx.compose.ui.res.painterResource
    import androidx.credentials.CredentialManager
    import androidx.credentials.exceptions.GetCredentialCancellationException
    import androidx.credentials.exceptions.GetCredentialCustomException
    import androidx.credentials.exceptions.NoCredentialException
    import androidx.credentials.GetCredentialRequest
    import com.google.android.libraries.identity.googleid.GetGoogleIdOption
    import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
    import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
    import java.security.SecureRandom
    import java.util.Base64
    import kotlinx.coroutines.CoroutineScope
    import androidx.compose.runtime.LaunchedEffect
    import kotlinx.coroutines.delay
    import kotlinx.coroutines.launch
    
  3. Selanjutnya, kita harus membuat fungsi untuk membuat permintaan Bottom Sheet. Tempelkan kode ini di bawah Class 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)
   }

Mari kita uraikan fungsi kode ini:

fun BottomSheet(webClientId: String) {...}: Membuat fungsi bernama BottomSheet yang mengambil satu argumen string bernama webClientid

  • val context = LocalContext.current: Mengambil konteks Android saat ini. Hal ini diperlukan untuk berbagai operasi, termasuk meluncurkan komponen UI.
  • LaunchedEffect(Unit) { ... }: LaunchedEffect adalah composable Jetpack Compose yang memungkinkan Anda menjalankan fungsi penangguhan (fungsi yang dapat menjeda dan melanjutkan eksekusi) dalam siklus proses composable. Unit sebagai kunci berarti efek ini hanya akan berjalan sekali saat composable pertama kali diluncurkan.
    • val googleIdOption: GetGoogleIdOption = ...: Membuat objek GetGoogleIdOption. Objek ini mengonfigurasi jenis kredensial yang diminta dari Google.
      • .Builder(): Pola builder digunakan untuk mengonfigurasi opsi.
      • .setFilterByAuthorizedAccounts(true): Menentukan apakah pengguna diizinkan untuk memilih dari semua Akun Google, atau hanya akun yang telah mengizinkan aplikasi. Dalam hal ini, setelannya adalah benar, yang berarti permintaan akan dilakukan menggunakan kredensial yang sebelumnya telah diizinkan pengguna untuk digunakan dengan aplikasi ini, jika ada.
      • .setServerClientId(webClientId): Menetapkan ID klien server, yang merupakan ID unik untuk backend aplikasi Anda. Parameter ini diperlukan untuk mendapatkan token ID.
      • .setNonce(generateSecureRandomNonce()): Menetapkan nonce, nilai acak, untuk mencegah serangan replay dan memastikan token ID dikaitkan dengan permintaan tertentu.
      • .build(): Membuat objek GetGoogleIdOption dengan konfigurasi yang ditentukan.
    • val request: GetCredentialRequest = ...: Membuat objek GetCredentialRequest. Objek ini merangkum seluruh permintaan kredensial.
      • .Builder(): Memulai pola builder untuk mengonfigurasi permintaan.
      • .addCredentialOption(googleIdOption): Menambahkan googleIdOption ke permintaan, yang menentukan bahwa kita ingin meminta token ID Google.
      • .build(): Membuat objek GetCredentialRequest.
    • val e = signIn(request, context): Tindakan ini mencoba membuat pengguna login dengan permintaan yang dibuat dan konteks saat ini. Hasil fungsi signIn disimpan di e. Variabel ini akan berisi hasil yang berhasil atau pengecualian.
    • if (e is NoCredentialException) { ... }: Ini adalah pemeriksaan bersyarat. Jika fungsi signIn gagal dengan NoCredentialException, berarti tidak ada akun yang sebelumnya diizinkan yang tersedia.
      • val googleIdOptionFalse: GetGoogleIdOption = ...: Jika signIn sebelumnya gagal, bagian ini akan membuat GetGoogleIdOption baru.
      • .setFilterByAuthorizedAccounts(false): Ini adalah perbedaan penting dari opsi pertama. Tindakan ini akan menonaktifkan pemfilteran akun yang sah, yang berarti bahwa Akun Google apa pun di perangkat dapat digunakan untuk login.
      • val requestFalse: GetCredentialRequest = ...: GetCredentialRequest baru dibuat dengan googleIdOptionFalse.
      • signIn(requestFalse, context): Tindakan ini mencoba membuat pengguna login dengan permintaan baru yang memungkinkan penggunaan akun apa pun.

Pada dasarnya, kode ini menyiapkan permintaan ke Credential Manager API untuk mengambil token ID Google bagi pengguna, menggunakan konfigurasi yang diberikan. GetCredentialRequest kemudian dapat digunakan untuk meluncurkan UI pengelola kredensial, tempat pengguna dapat memilih Akun Google mereka dan memberikan izin yang diperlukan.

fun generateSecureRandomNonce(byteLength: Int = 32): String: Ini menentukan fungsi bernama generateSecureRandomNonce. Fungsi ini menerima argumen bilangan bulat byteLength (dengan nilai default 32) yang menentukan panjang nonce yang diinginkan dalam byte. Metode ini menampilkan String, yang akan menjadi representasi byte acak berenkode Base64.

  • val randomBytes = ByteArray(byteLength): Membuat array byte dengan byteLength yang ditentukan untuk menyimpan byte acak.
  • SecureRandom.getInstanceStrong().nextBytes(randomBytes):
    • SecureRandom.getInstanceStrong(): Ini mendapatkan generator angka acak yang kuat secara kriptografis. Hal ini sangat penting untuk keamanan, karena memastikan angka yang dihasilkan benar-benar acak dan tidak dapat diprediksi. Metode ini menggunakan sumber entropi terkuat yang tersedia di sistem.
    • .nextBytes(randomBytes): Metode ini mengisi array randomBytes dengan byte acak yang dihasilkan oleh instance SecureRandom.
  • return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes):
    • Base64.getUrlEncoder(): Mendapatkan encoder Base64 yang menggunakan alfabet aman untuk URL (menggunakan - dan _ sebagai pengganti + dan /). Hal ini penting karena memastikan string yang dihasilkan dapat digunakan dengan aman di URL tanpa memerlukan encoding lebih lanjut.
    • .withoutPadding(): Menghapus karakter padding dari string berenkode Base64. Hal ini sering kali diinginkan untuk membuat nonce sedikit lebih pendek dan lebih ringkas.
    • .encodeToString(randomBytes): Mengenkode randomBytes menjadi string Base64 dan menampilkannya.

Singkatnya, fungsi ini menghasilkan nonce acak yang kuat secara kriptografis dengan panjang yang ditentukan, mengenkodekannya menggunakan Base64 yang aman untuk URL, dan menampilkan string yang dihasilkan. Ini adalah praktik standar untuk membuat nonce yang aman digunakan dalam konteks yang sensitif terhadap keamanan.

Buat permintaan login

Setelah dapat membuat permintaan login, kita dapat menggunakan Credential Manager untuk menggunakannya saat login. Untuk melakukannya, kita perlu membuat fungsi yang menangani penerusan permintaan login menggunakan Pengelola Kredensial, sekaligus menangani pengecualian umum yang mungkin kita temui.

Anda dapat menempelkan fungsi ini di bawah fungsi BottomSheet() untuk melakukannya.

//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
}

Sekarang mari kita uraikan fungsi kode di sini:

suspend fun signIn(request: GetCredentialRequest, context: Context): Exception?: Kode ini menentukan fungsi penangguhan bernama signIn. Artinya, operasi ini dapat dijeda dan dilanjutkan tanpa memblokir thread utama.Operasi ini menampilkan Exception? yang akan bernilai null jika login berhasil atau pengecualian tertentu jika login gagal.

Fungsi ini menggunakan dua parameter:

  • request: Objek GetCredentialRequest, yang berisi konfigurasi untuk jenis kredensial yang akan diambil (misalnya, ID Google).
  • context: Konteks Android yang diperlukan untuk berinteraksi dengan sistem.

Untuk isi fungsi:

  • val credentialManager = CredentialManager.create(context): Membuat instance CredentialManager, yang merupakan antarmuka utama untuk berinteraksi dengan Credential Manager API. Beginilah cara aplikasi akan memulai alur login.
  • val failureMessage = "Sign in failed!": Menentukan string (failureMessage) yang akan ditampilkan dalam toast saat login gagal.
  • var e: Exception? = null: Baris ini menginisialisasi variabel e untuk menyimpan pengecualian yang mungkin terjadi selama proses, dimulai dengan null.
  • delay(250): Memperkenalkan penundaan 250 milidetik. Ini adalah solusi untuk potensi masalah saat NoCredentialException dapat langsung muncul saat aplikasi dimulai, terutama saat menggunakan alur BottomSheet. Hal ini memberi waktu bagi sistem untuk melakukan inisialisasi pengelola kredensial.
  • try { ... } catch (e: Exception) { ... }:Blok try-catch digunakan untuk penanganan error yang andal. Hal ini memastikan bahwa jika terjadi error selama proses login, aplikasi tidak akan mengalami error, dan dapat menangani pengecualian dengan baik.
    • val result = credentialManager.getCredential(request = request, context = context): Di sinilah panggilan sebenarnya ke Credential Manager API terjadi dan memulai proses pengambilan kredensial. Fungsi ini mengambil permintaan dan konteks sebagai input dan akan menampilkan UI kepada pengguna untuk memilih kredensial. Jika berhasil, hasil yang berisi kredensial yang dipilih akan ditampilkan. Hasil operasi ini, GetCredentialResponse, disimpan dalam variabel result.
    • Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show():Menampilkan pesan toast singkat yang menunjukkan bahwa login berhasil.
    • Log.i(TAG, "Sign in Successful!"): Mencatat pesan yang menyenangkan dan berhasil ke logcat.
    • catch (e: GetCredentialException): Menangani pengecualian jenis GetCredentialException. Ini adalah class induk untuk beberapa pengecualian spesifik yang dapat terjadi selama proses pengambilan kredensial.
    • catch (e: GoogleIdTokenParsingException): Menangani pengecualian yang terjadi saat terjadi error dalam mengurai Token ID Google.
    • catch (e: NoCredentialException): Menangani NoCredentialException, yang ditampilkan saat tidak ada kredensial yang tersedia untuk pengguna (misalnya, pengguna belum menyimpan kredensial, atau tidak memiliki Akun Google).
      • Yang penting, fungsi ini menampilkan pengecualian yang disimpan di e, NoCredentialException, sehingga pemanggil dapat menangani kasus tertentu jika tidak ada kredensial yang tersedia.
    • catch (e: GetCredentialCustomException): Menangani pengecualian kustom yang mungkin ditampilkan oleh penyedia kredensial.
    • catch (e: GetCredentialCancellationException): Menangani GetCredentialCancellationException, yang ditampilkan saat pengguna membatalkan proses login.
    • Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show(): Menampilkan pesan toast yang menunjukkan bahwa login gagal menggunakan failureMessage.
    • Log.e(TAG, "", e): Mencatat pengecualian ke logcat Android menggunakan Log.e, yang digunakan untuk error. Hal ini akan mencakup stacktrace pengecualian untuk membantu proses debug. Pesan ini juga menyertakan emotikon marah untuk bersenang-senang.
  • return e: Fungsi menampilkan pengecualian jika ada yang tertangkap, atau null jika proses login berhasil.

Singkatnya, kode ini menyediakan cara untuk menangani login pengguna menggunakan Credential Manager API, mengelola operasi asinkron, menangani potensi error, dan memberikan masukan kepada pengguna melalui toast dan log sambil menambahkan sentuhan humor pada penanganan error.

Menerapkan alur sheet bawah di aplikasi

Sekarang kita dapat menyiapkan panggilan untuk memicu alur BottomSheet di kelas MainActivity menggunakan kode berikut dan Client ID Aplikasi Web yang kita salin dari Konsol Google Cloud sebelumnya:

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)
                }
            }
        }
    }
}

Sekarang kita dapat menyimpan project (File > Save) dan menjalankannya:

  1. Tekan tombol run:Menjalankan Project
  2. Setelah aplikasi Anda berjalan di emulator, Anda akan melihat BottomSheet login muncul. Lanjutkan dan klik Lanjutkan untuk menguji loginSheet Bawah
  3. Anda akan melihat pesan Toast yang menunjukkan bahwa login berhasil.Keberhasilan Sheet Bawah

7. Alur tombol

GIF Alur Tombol

Alur Tombol untuk Login dengan Google mempermudah pengguna mendaftar atau login ke aplikasi Android Anda menggunakan Akun Google yang sudah mereka miliki. Pengguna akan melihatnya jika menutup panel bawah atau lebih memilih menggunakan Akun Google mereka secara eksplisit untuk login atau mendaftar. Bagi developer, hal ini berarti orientasi yang lebih lancar dan lebih sedikit hambatan selama pendaftaran.

Meskipun hal ini dapat dilakukan dengan tombol Jetpack Compose bawaan, kita akan menggunakan ikon merek yang telah disetujui sebelumnya dari halaman Panduan Branding Login dengan Google.

Menambahkan ikon merek ke project

  1. Download ZIP ikon merek yang telah disetujui sebelumnya di sini
  2. Buka kompresi signin-assest.zip dari folder download Anda (langkah ini akan bervariasi berdasarkan sistem operasi komputer Anda). Sekarang Anda dapat membuka folder signin-assets dan melihat ikon yang tersedia. Untuk Codelab ini, kita akan menggunakan signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png.
  3. Menyalin file
  4. Tempelkan ke project di Android Studio di bagian res > drawable dengan mengklik kanan folder drawable dan mengklik Paste (Anda mungkin harus meluaskan folder res untuk melihatnya)Drawable
  5. Dialog akan muncul yang meminta Anda mengganti nama file dan mengonfirmasi direktori tempat file tersebut akan ditambahkan. Ganti nama aset menjadi siwg_button.png, lalu klik OkeMenambahkan Tombol

Kode alur tombol

Kode ini akan menggunakan fungsi signIn() yang sama dengan yang digunakan untuk BottomSheet(), tetapi menggunakan GetSignInWithGoogleOption, bukan GetGoogleIdOption, karena alur ini tidak memanfaatkan kredensial dan kunci sandi yang disimpan di perangkat untuk menampilkan opsi login. Berikut adalah kode yang dapat Anda tempel di bawah fungsi 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)
    )
}

Untuk menguraikan fungsi kode tersebut:

fun ButtonUI(webClientId: String): Ini mendeklarasikan fungsi bernama ButtonUI yang menerima webClientId (ID klien project Google Cloud Anda) sebagai argumen.

val context = LocalContext.current: Mengambil konteks Android saat ini. Hal ini diperlukan untuk berbagai operasi, termasuk meluncurkan komponen UI.

val coroutineScope = rememberCoroutineScope(): Membuat cakupan coroutine. Hal ini digunakan untuk mengelola tugas asinkron, sehingga kode dapat berjalan tanpa memblokir thread utama. rememberCoroutineScope() adalah fungsi composable dari Jetpack Compose yang akan menyediakan cakupan yang terikat dengan siklus proses composable.

val onClick: () -> Unit = { ... }: Ini membuat fungsi lambda yang akan dieksekusi saat tombol diklik. Fungsi lambda akan:

  • val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(serverClientId = webClientId).setNonce(generateSecureRandomNonce()).build(): Bagian ini membuat objek GetSignInWithGoogleOption. Objek ini digunakan untuk menentukan parameter untuk proses "Login dengan Google", yang memerlukan webClientId dan nonce (string acak yang digunakan untuk keamanan).
  • val request: GetCredentialRequest = GetCredentialRequest.Builder().addCredentialOption(signInWithGoogleOption).build(): Ini membangun objek GetCredentialRequest. Permintaan ini akan digunakan untuk mendapatkan kredensial pengguna menggunakan Credential Manager. GetCredentialRequest menambahkan GetSignInWithGoogleOption yang dibuat sebelumnya sebagai opsi, untuk meminta kredensial "Login dengan Google".
  • coroutineScope.launch { ... }: CoroutineScope untuk mengelola operasi asinkron (menggunakan coroutine).
    • signIn(request, context): memanggil fungsi signIn() yang telah ditentukan sebelumnya

Image(...): Ini merender gambar menggunakan painterResource yang memuat gambar R.drawable.siwg_button

  • Modifier.fillMaxSize().clickable(enabled = true, onClick = onClick):
    • fillMaxSize(): Membuat gambar mengisi ruang yang tersedia.
    • clickable(enabled = true, onClick = onClick): Membuat gambar dapat diklik, dan saat diklik, gambar akan menjalankan fungsi lambda onClick yang sebelumnya ditentukan.

Singkatnya, kode ini menyiapkan tombol "Login dengan Google" di UI Jetpack Compose. Saat tombol diklik, tombol tersebut akan menyiapkan permintaan kredensial untuk meluncurkan Pengelola Kredensial dan memungkinkan pengguna login dengan Akun Google mereka.

Sekarang kita perlu mengupdate class MainActivity untuk menjalankan fungsi 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)
                    }
                }
            }
        }
    }
}

Sekarang kita dapat menyimpan project (File > Save) dan menjalankannya:

  1. Tekan tombol run:Menjalankan Project
  2. Setelah aplikasi berjalan di emulator, BottomSheet akan muncul. Klik di luar jendela untuk menutupnya.Ketuk di sini
  3. Sekarang Anda akan melihat tombol yang kita buat ditampilkan di aplikasi. Lanjutkan dan klik untuk melihat dialog loginDialog Login
  4. Klik akun Anda untuk login.

8. Kesimpulan

Anda telah menyelesaikan codelab ini. Untuk mengetahui informasi atau bantuan selengkapnya terkait Login dengan Google di Android, lihat bagian Pertanyaan Umum di bawah:

Pertanyaan Umum (FAQ)

Kode MainActivity.kt lengkap

Berikut adalah kode lengkap untuk MainActivity.kt sebagai referensi:

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
}