1. Sebelum memulai
Pelajari cara menerapkan Login dengan Google di Android menggunakan Credential Manager.
Prasyarat
- Pemahaman dasar tentang Kotlin untuk pengembangan Android.
- Pemahaman dasar tentang Jetpack Compose (pelajari lebih lanjut).
Yang akan Anda pelajari
- Buat project Google Cloud dan klien OAuth.
- Terapkan alur login Sheet Bawah.
- Terapkan alur login Tombol eksplisit.
Yang Anda perlukan
- Android Studio diinstal.
- Komputer yang memenuhi persyaratan sistem Android Studio dan Emulator.
- Java Development Kit (JDK) terinstal.
2. Membuat project Android Studio
Untuk memulai, buat project baru di Android Studio:
- Buka Android Studio, lalu klik New Project.

- Pilih Phone and Tablet > Empty Activity, lalu klik Next.

- Konfigurasi setelan project:
- Nama: Pilih nama project.
- Nama paket: Gunakan nama default atau pilih nama Anda sendiri.
- SDK Minimum: Pilih versi stabil terbaru atau > Android 14.

- Klik Finish dan tunggu hingga build project awal selesai.

3. Menyiapkan project Google Cloud
Membuat project Google Cloud
- Buka Konsol Google Cloud, lalu pilih atau buat project.

- Buka APIs & Services > layar izin OAuth.

- Klik Mulai dan isi kolom yang wajib diisi:
- Nama Aplikasi: Gunakan nama aplikasi Android Anda.
- Email Dukungan Pengguna: Pilih Akun Google Anda.
- Audiens: Pilih Eksternal.
- Info Kontak: Masukkan alamat email Anda.

- Tinjau Google API Services: Kebijakan Data Pengguna, lalu klik Create.

Menyiapkan klien OAuth
Anda perlu membuat klien Web dan klien Android di Konsol Google Cloud untuk mendapatkan client ID untuk autentikasi.
- Klien Android: Mengamankan permintaan dengan memverifikasi nama paket dan tanda tangan SHA-1 aplikasi Anda.
- Klien web: Bertindak sebagai klien backend untuk layanan Login dengan Google.
Buat klien OAuth 2.0 Android
- Di halaman Clients, klik Create Client, lalu pilih Android sebagai Application type.

- Masukkan nama paket aplikasi Anda (cocok dengan baris 1
MainActivity.kt). - Buat tanda tangan SHA-1 Anda. Buka terminal Android Studio dan jalankan:macOS/Linux:
Windows:keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
keytool -list -v -keystore "C:\Users\USERNAME\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
- Salin sidik jari SHA-1 dari output perintah, tempelkan ke kolom SHA-1 fingerprint di konsol, lalu klik Create.

Buat klien OAuth 2.0 Web
- Klik Create Client lagi, lalu pilih Web Application sebagai Application type.
- Beri nama klien web Anda, biarkan kolom URL/Asal kosong, lalu klik Buat.

- Salin Client ID yang dibuat dari dialog konfirmasi. Anda akan menggunakannya dalam kode Kotlin.

4. Menyiapkan Perangkat Virtual Android
Untuk menguji aplikasi, Anda dapat menggunakan perangkat Android fisik atau Perangkat Virtual Android (AVD).
Membuat dan menjalankan AVD
- Di Android Studio, buka Pengelola Perangkat, klik Create Virtual Device (atau ikon +), lalu pilih Medium Phone.
- Pilih versi stabil terbaru sebagai image sistem, lalu klik Finish.
- Luncurkan emulator dengan mengklik ikon Play/Run di samping perangkat.

Login ke Akun Google di perangkat
- Di emulator, buka aplikasi Setelan, lalu buka Google.
- Klik Login ke Akun Google Anda dan ikuti petunjuknya.

5. Menambahkan dependensi
Tambahkan library yang diperlukan untuk autentikasi dan integrasi ID Google ke project Anda:
- Buka File > Project Structure > Dependencies > app.
- Klik + > Library Dependency, telusuri
com.google.android.libraries.identity.googleid:googleid, lalu pilih versi terbaru (misalnya,1.1.1). - Klik + > Library Dependency lagi, telusuri
play-services-auth, lalu pilih pustaka dengan Group IDcom.google.android.gms. - Klik OK untuk menerapkan perubahan dan menyinkronkan project Anda.

6. Menerapkan alur Sheet Bawah

Alur Sheet Bawah memanfaatkan Credential Manager API untuk menyediakan cara yang lebih mudah bagi pengguna untuk login ke aplikasi Anda menggunakan Akun Google mereka di Android. Didesain untuk kecepatan dan kenyamanan, terutama bagi pengguna yang kembali, alur ini harus dipicu saat peluncuran aplikasi.
Buat permintaan login
- Untuk memulai, buka
MainActivity.ktdan hapus fungsiGreeting()danGreetingPreview()default. - Tambahkan pernyataan impor berikut setelah pernyataan yang ada, dimulai dari baris 3:
import android.content.Context import android.os.Build import android.util.Log import android.widget.Toast import androidx.annotation.RequiresApi import androidx.compose.foundation.Image import androidx.compose.foundation.clickable 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.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.credentials.CredentialManager import androidx.credentials.CustomCredential import androidx.credentials.GetCredentialRequest import androidx.credentials.exceptions.GetCredentialCancellationException import androidx.credentials.exceptions.GetCredentialCustomException import androidx.credentials.exceptions.GetCredentialException import androidx.credentials.exceptions.NoCredentialException import com.google.android.libraries.identity.googleid.GetGoogleIdOption import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException import java.security.SecureRandom import java.util.Base64 import kotlinx.coroutines.delay import kotlinx.coroutines.launch const val TAG = "MainActivity" - Tambahkan fungsi Composable ini di bawah class
MainActivitydalam fileMainActivity.ktAnda:@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) }
Perincian Kode
LaunchedEffect(Unit): Memicu alur login segera saat Composable pertama kali ditampilkan.GetGoogleIdOption.Builder(): Mengonfigurasi permintaan token ID Google.setFilterByAuthorizedAccounts(true): Pertama-tama mencoba login diam-diam dengan memfilter akun yang telah diizinkan pengguna untuk aplikasi ini, sehingga meminimalkan hambatan bagi pengguna yang kembali.setNonce(...): Meneruskan nonce acak aman yang dibuat untuk setiap permintaan olehgenerateSecureRandomNonce()untuk mencegah serangan replay.
signIn(request, context): Mengeksekusi permintaan. Jika gagal denganNoCredentialException(artinya tidak ada akun yang sebelumnya diizinkan), alurnya akan kembali kesetFilterByAuthorizedAccounts(false)agar pengguna dapat memilih dari akun Google yang login di perangkat.
Buat permintaan login
Setelah permintaan login dibuat, Anda dapat menggunakan Credential Manager untuk menyelesaikan proses login. Buat fungsi bernama signIn yang menjalankan permintaan dan menangani pengecualian umum yang mungkin terjadi.
Tambahkan fungsi ini di bawah fungsi BottomSheet dalam file MainActivity.kt Anda:
@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!"
//using delay() here helps prevent NoCredentialException when the BottomSheet Flow is triggered
//on the initial running of our app
delay(250)
return 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())
val credential = result.credential
if (credential is CustomCredential &&
credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data)
Log.i(TAG, "Signed in as: ${googleIdTokenCredential.id}")
}
Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
Log.i(TAG, "(☞゚ヮ゚)☞ Sign in Successful! ☜(゚ヮ゚☜)")
null
} catch (e: GoogleIdTokenParsingException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with parsing received GoogleIdToken", e)
e
} catch (e: NoCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": No credentials found", e)
e
} catch (e: GetCredentialCancellationException) {
Toast.makeText(context, "Sign-in cancelled", Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Sign-in was cancelled", e)
e
} catch (e: GetCredentialCustomException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with custom credential request", e)
e
} catch (e: GetCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Failure getting credentials", e)
e
}
}
Perincian Kode
credentialManager.getCredential(...): Memanggil Credential Manager API untuk menampilkan dialog atau sheet bawah pemilih akun sistem.delay(250): Dijeda sebentar untuk mencegah kondisi race saat panel bawah dipicu segera saat aplikasi dimulai sebelum layanan Credential Manager selesai melakukan inisialisasi.- Penanganan Pengecualian: Menangkap dan mencatat error kredensial umum (misalnya, pembatalan, kredensial yang tidak ada, atau masalah parsing token) dan memberikan masukan pengguna menggunakan toast.
Memicu alur Panel Bawah
Perbarui class MainActivity untuk memanggil BottomSheet() saat startup. Ganti YOUR_CLIENT_ID_HERE dengan ID klien Aplikasi web Anda:
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)
}
}
}
}
}
Simpan project Anda (File > Save) dan jalankan aplikasi:
- Tekan tombol run:

- Saat aplikasi diluncurkan di emulator, Bottom Sheet login akan muncul. Klik Lanjutkan untuk menguji alur.

- Notifikasi toast akan muncul, yang mengonfirmasi bahwa login berhasil.

7. Menerapkan alur Tombol

Alur Tombol memberikan opsi eksplisit bagi pengguna untuk login atau mendaftar. Penggunaan branding standar memastikan pengalaman yang konsisten. Gunakan aset yang telah disetujui sebelumnya dan sesuai dengan Pedoman Branding Login dengan Google.
Menambahkan ikon merek
- Download aset merek di sini dan ekstrak ZIP.
- Salin
signin-assets/Android/png@2x/neutral/android_neutral_sq_SI@2x.png. - Di Android Studio, tempelkan file ke folder res > drawable, ganti namanya menjadi
siwg_button.png, lalu klik OK.
Kode alur tombol
Alur ini menggunakan kembali fungsi bantuan signIn yang sama, tetapi meneruskan GetSignInWithGoogleOption, bukan GetGoogleIdOption. Tidak seperti alur Sheet Bawah, alur tombol eksplisit tidak melakukan pra-pemfilteran atau otomatis meminta kredensial tersimpan atau kunci sandi. Tempel fungsi Composable ini 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)
)
}
Perincian Kode
GetSignInWithGoogleOption: Tidak seperti alur Lembar Bawah, alur tombol eksplisit menggunakan opsi ini untuk meminta pengguna memilih Akun Google mereka tanpa pemfilteran otomatis.coroutineScope.launch: Meluncurkan coroutine untuk menjalankan fungsisignInpenangguhan secara asinkron saat tombol diklik.Image: Menampilkan drawablesiwg_buttonbermerek dan melampirkan pemroses klik untuk memicu alur.
Menambahkan Tombol ke tata letak UI
Perbarui tata letak MainActivity untuk menampilkan BottomSheet otomatis dan ButtonUI eksplisit yang disusun secara vertikal:
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)
}
}
}
}
}
}
Menguji alur Tombol
- Jalankan aplikasi.
- Tutup Sheet Bawah awal dengan mengklik di luar area sheet.
- Klik tombol Login dengan Google untuk meluncurkan dialog login, lalu pilih akun Anda.

- Verifikasi hasilnya: periksa Logcat untuk mengonfirmasi cetakan nama pengguna/alamat email Anda.
8. Kesimpulan
Selamat! Anda telah berhasil menerapkan Login dengan Google menggunakan Android Credential Manager.
Referensi lainnya
- Dokumentasi Android Credential Manager
- Panduan Pemecahan Masalah Credential Manager
- FAQ Pengelola Kredensial
- Dukungan StackOverflow
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.util.Log
import android.widget.Toast
import androidx.credentials.exceptions.GetCredentialException
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)
}
}
}
}
}
}
@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()
coroutineScope.launch {
signIn(request, context)
}
}
Image(
painter = painterResource(id = R.drawable.siwg_button),
contentDescription = "",
modifier = Modifier
.fillMaxSize()
.clickable(onClick = onClick)
)
}
fun generateSecureRandomNonce(byteLength: Int = 32): String {
val randomBytes = ByteArray(byteLength)
SecureRandom.getInstanceStrong().nextBytes(randomBytes)
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes)
}
@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!"
//using delay() here helps prevent NoCredentialException when the BottomSheet Flow is triggered
//on the initial running of our app
delay(250)
return 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())
val credential = result.credential
if (credential is CustomCredential &&
credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data)
Log.i(TAG, "Signed in as: ${googleIdTokenCredential.id}")
}
Toast.makeText(context, "Sign in successful!", Toast.LENGTH_SHORT).show()
Log.i(TAG, "(☞゚ヮ゚)☞ Sign in Successful! ☜(゚ヮ゚☜)")
null
} catch (e: GoogleIdTokenParsingException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with parsing received GoogleIdToken", e)
e
} catch (e: NoCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": No credentials found", e)
e
} catch (e: GetCredentialCancellationException) {
Toast.makeText(context, "Sign-in cancelled", Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Sign-in was cancelled", e)
e
} catch (e: GetCredentialCustomException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Issue with custom credential request", e)
e
} catch (e: GetCredentialException) {
Toast.makeText(context, failureMessage, Toast.LENGTH_SHORT).show()
Log.e(TAG, failureMessage + ": Failure getting credentials", e)
e
}
}