๊ตฌ๊ธ One-Tap ๋ก๊ทธ์ธ์ ํ์ด์ด๋ฒ ์ด์ค๋ก ๊ฒฝ์ ํด์ ๊ตฌํํ๋ค.
๊ธฐ์กด ๊ตฌ๊ธ ๋ก๊ทธ์ธ ๊ตฌํํ ์ฝ๋๋ฅผ ๋จผ์ ๋ณด์. ํ์ด์ด๋ฒ ์ด์ค ๊ณต์ ๋ฌธ์์ ์๋ ์ค๋ํซ์ ์ ์๋ํ๊ธด ํ์ง๋ง ๋๋ฌด ์ค๋๋ผ์ ๋ด ๋ง์๋๋ก ๊ฐ์กฐํ๋ค.
ํน์ ๊ตฌ๊ธ ๋ก๊ทธ์ธ์ ์์์ฝ๋๋๋ก ๋ฐ๋ผํ์ ๋
developer console is not set up correctly.
service: oauth2:openid amgv: error when calling server using gmsnetworkstack.
๋ก๊ทธ์ ์ด๋ฐ ๋ฌธ์ฅ์ด ๋ณด์ธ๋ค๋ฉด, firebase ํ๋ก์ ํธ ์ค์ ์ SHA-1 ๋ฑ๋ก์ด ์๋ผ์๋ ์ํ์ด๋ฏ๋ก SHA-1๋ฑ๋ก์ ํด์ฃผ๋ฉด ํด๊ฒฐ๋ ๊ฒ์ด๋ค.
ํ๋ํ๋ ๋ฏ์ด๋ณด์. ํ์ฌ ํ๋ก์ ํธ์์ SAA ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ๊ณ ์์ด์ Fragment๊ธฐ์ค์ผ๋ก ์์ฑ๋์๋ค.
์์ ์ฝ๋์์ onActivityResult๋ก ์์ฑ๋์ด์๋ ๋ถ๋ถ์ด ์๋ค. ๊ตฌ๊ธ ๋ก๊ทธ์ธ ์กํฐ๋นํฐ๋ฅผ ๋์ฐ๊ณ , ๊ทธ๊ฒ์ ๋ํ ์๋ต๊ฐ์ ๋ฐ์ ์ฒ๋ฆฌํ๋ ๋ถ๋ถ์ธ๋ฐ ์ด ์ฝ๋๋ deprecated๋์ง ์ค๋๋ค.
๊ทธ๋์ ActivityResultLauncher๋ฅผ ์ฌ์ฉํด ์ฝ๋ ๊ตฌ์กฐ๋ฅผ ์ข ๋ฐ๊ฟ๋ดค๋ค. ์ด๊ธฐํ ์์ ์ด๋ ์ฌ์ฉ์์ ์ด ๋ค๋ฅด๋ฏ๋ก lateinit์ผ๋ก ์ ์ธํด์คฌ๋ค.
private lateinit var googleSignInLauncher: ActivityResultLauncher<Intent>
๊ทธ๋ฆฌ๊ณ ์ด ๋ฐ์ฒ๋ฅผ ์ด๊ธฐํํด์ค์ผํ๋๋ฐ fragment์์๋ ์ด๋์ ์ด๊ธฐํ ํด์ค์ผํ ๊น?
View๊ฐ ์์ฑ๋ ๋ค์ ์ด๊ธฐํํ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ๊ทธ๋์ onCreate์์ View๊ฐ ์์ฑ๋๊ธฐ ์ ์ ์ด๊ธฐํ๋ฅผ ํด์ค์ผํ๋ค.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
googleSignInLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
googleSignIn(result.data)
}
}
result์ dataํ๋กํผํฐ์ ๋ด๊ธด ๊ฒ Intent๋ค. ์ด๊ฑธ ๋ฐ์์ ๋ก๊ทธ์ธ ์ฒ๋ฆฌ๋ฅผ ํ๋ฉด ๋๋ค.
์ด๋ ๊ฒ ๋ง๋ launcher๋ ํธ์ถํ ๋ ์ธ์๋ก singInIntent๋ฅผ ๋ฐ๋๋ค.
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(activity.getString(R.string.default_web_client_id))
.requestEmail()
.build()
val googleSignInClient = GoogleSignIn.getClient(activity, gso)
googleSignInClient.signInIntent // <- ์ด๊ฑธ ์ฌ์ฉํ๋ค
์ด ์ฝ๋๋ฅผ ๊ทธ๋๋ก ๊ฐ๋ค์ฐ๋ฉด ์๋๊ณ ๊ทธ๋ฅ ํ๋ฆ๋ง ๋ณด๋ฉด ๋๋ค.
requestIdToken์ ์ฌ์ฉ๋๋ default_web_client_id๋ ํ์ด์ด๋ฒ ์ด์ค auth๋ฅผ ์ถ๊ฐํ๋ค๋ฉด ๋น๋ํ ๋ ์๋์ผ๋ก ์๊ธฐ๋๋ฐ, google-services 4.4.1๋ฒ์ ์ ๊ฒฝ์ฐ ํ์ผ์ด ๋ฉ์ฉกํ๊ฒ ์์ด๋ ์๋ค๊ณ ๋์ค๋ IDE๋ฒ๊ทธ๊ฐ ์๋ค.(๋ฐํ์ ๋์ ๋ฌธ์ ๋ ์๋ค.) 4.3.8๋ก ๋ฒ์ ์ ๋ด๋ฆฌ๋๊น ์ฌ๋ผ์ก๋๋ฐ, ์๋ ํ์ด์ด๋ฒ ์ด์ค๊ฐ ๋ฌผ๋ ค์๋๊ฒ ๋ง๋ค๋ณด๋ ๋ฒ์ ์์ ์ด ์ข ๋๋ ค์ ๋ค.
private fun googleSignIn(data: Intent?) {
runCatching {
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
task.getResult(ApiException::class.java)
}.onSuccess { account ->
CoroutineScope(Dispatchers.Main).launch {
val user = loginRepoInstance.firebaseAuthWithGoogle(account.idToken!!)
updateUI(user)
}
}.onFailure { e ->
Log.w(TAG, "Google sign in failed $e")
}
}
updateUI๋ ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ์ ๋ ui ์
๋ฐ์ดํธ๋ฅผ ํด์ฃผ๋ ๋ถ๋ถ์ด๋ผ์ ์ด๊ฑด ๊ตฌํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋ ๋์ด๊ฐ๊ณ , runCatching์ผ๋ก try-catch๋ฅผ ๋์ ํด ๊ฐ๋
์ฑ์ ๋์ฌ๋ดค๋ค.
๋ฌธ์ ๋ ์์์ client๋ฐ์์ฌ ๋๋ ์ฌ์ฉ๋์๋ GoogleSignIn์ด deprecated๋์ด์ Credential Manager๋ฅผ ์ฌ์ฉํด์ผ๋๋ค๋ ์ ์ด๋ค. ๊ทธ๋์ผ ์๋ ์ฌ์ง์ฒ๋ผ ์ค ๊ทธ์ด์ง ๋ชจ์ต์ ์น์๋ฒ๋ฆด ์๊ฐ ์๋ค.
# CredentialManager ๋ก Migrationํ๊ธฐ
๊ณต์๋ฌธ์์ ๋ง์ด๊ทธ๋ ์ด์ ๊ฐ์ด๋๊ฐ ์๋๋ฐ ์์งํ ์ ๋ฐ์ดํธ๋ ๋๋ฆฌ๊ณ ์ข ๋์ ์ ์๋ค์ด์จ๋ค.
implementation("androidx.credentials:credentials:<latest version>")
implementation("androidx.credentials:credentials-play-services-auth:<latest version>")
implementation("com.google.android.libraries.identity.googleid:googleid:<latest version>")
credential๋ฒ์ ์ 1.3.0-alpha03, googleid๋ 1.1.0์ด 2024-05-11๊ธฐ์ค ์ต์ ๋ฒ์ ์ด๋ค.
Credential๋ก ๋ฐ๊พธ๊ฒ ๋๋ฉด ๋ฌ๋ผ์ง๋ ์ ์ด ๋ช๊ฐ์ง ์๊ธด๋ค.
- Activity/Fragment์์ ์ฒ๋ฆฌํ๋ ์ฝ๋๋ฅผ ๋ถ๋ฆฌํ ์ ์๋ค.
- ๋ก๊ทธ์ธ ์์ฒญํ๋ ๊ณผ์ ์ด suspend fun์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ฝ๋ฃจํด์ ๋ฌด์กฐ๊ฑด ์ฌ์ฉํด์ผ๋๋ค.
- ๋ก๊ทธ์ธ UI๊ฐ ์์๊ฒ ๋ฐ๋๋ค.
๊ทธ๋ฅ ๋ก๊ทธ์ธ ํ์ ์ด์๋๊ฒ ๋ฐํ ์ํธ๋ก ๋ฐ๋๋ค. ์ ๋๋ฉ์ด์ ๋ ๋ํด์ ธ์ ๋๋ฌด ๋ณด๊ธฐ ์ข๋ค.
๋ก๊ทธ์ธ์ ์ํํ ๋ ค๋ฉด credential์ ํตํด idToken์ ๊ฐ์ ธ์์ผํ๋๋ฐ ๊ทธ ๊ณผ์ ์ด suspend fun์ผ๋ก ์์ฑ๋์ด์๋ค.
suspend fun getCredential(
context: Context,
request: GetCredentialRequest,
): GetCredentialResponse = suspendCancellableCoroutine { continuation ->
// Any Android API that supports cancellation should be configured to propagate
// coroutine cancellation as follows:
val canceller = CancellationSignal()
continuation.invokeOnCancellation { canceller.cancel() }
val callback = object : CredentialManagerCallback<GetCredentialResponse,
GetCredentialException> {
override fun onResult(result: GetCredentialResponse) {
if (continuation.isActive) {
continuation.resume(result)
}
}
override fun onError(e: GetCredentialException) {
if (continuation.isActive) {
continuation.resumeWithException(e)
}
}
}
getCredentialAsync(
context,
request,
canceller,
// Use a direct executor to avoid extra dispatch. Resuming the continuation will
// handle getting to the right thread or pool via the ContinuationInterceptor.
Runnable::run,
callback)
}
๋งค๊ฐ๋ณ์๋ก๋ context(Intent ๋ ๋ฆฌ๋ ค๋ฉด ํ์ํ๋๊น), GetCredentialRequest ์ด๋ ๊ฒ ๋๊ฐ๊ฐ ํ์ํ๋ค.
์ค๋น ๊ณผ์ ์ด ์๋ค. credential manager ์ธ์คํด์ค๊ฐ ํ์ํ๊ณ , ๊ตฌ๊ธ๋ก๊ทธ์ธ์ ์ฌ์ฉํ google id option ์ธ์คํด์ค, ๊ทธ๋ฆฌ๊ณ idToken์ ์ป์ ๋ ํ์ํ get credential request ์ธ์คํด์ค ์ด๋ ๊ฒ ์ธ๊ฐ๋ฅผ ์ด๊ธฐํ ํด์ค์ผํ๋ค.
val credentialManager = CredentialManager.create(context)
val googleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(false)
.setAutoSelectEnabled(true)
.setServerClientId(context.getString(R.string.default_web_client_id))
.build()
val credentialRequest: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()
googleIdOption์์ setAutoSelectEnabled๋ฅผ ํ์ฑํํ๋ฉด, ๋ฐํ ์ํธ๊ฐ ์ฌ๋ผ๊ฐ๋ฉด์ ๊ณ์ ์ด ์๋์ ํ๋๋ค. ๋๋ ํ์ด์ด๋ฒ ์ด์ค auth๋ฅผ ์ฌ์ฉํ๋๊น ๋ฐ๋ก web client id๋ฅผ ๊ด๋ฆฌํ์ง๋ ์์๋ค.
setFilterByAuthorizedAccounts(false)๋ ์ด์ ์ ๋ก๊ทธ์ธ ํ ๊ณ์ ์ ํ์ธํ ์ง ์ ํ๋ ์ต์ ์ด๋ค.
googleIdOption๊ณผ credentialRequest๋ฅผ buildํ๋ ๊ณผ์ ์ด ์ฝ๊ฐ ์ง์ฐ์๊ฐ์ด ์๋ค๊ณ ๋๊ปด์ ์ด๊ฑด Splash Activity์์ ์ฒ๋ฆฌํ๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ค์๋ค.
์ด์ ๊ตฌํ์ ๋ณด์. ๋ด๊ฐ ์์ฑํ ํจ์๋ ์๋์ ๊ฐ๋ค.
suspend fun requestGoogleLogin(
context: Context,
onSuccessListener: (FirebaseUser?) -> Unit,
) {
runCatching {
credentialManager.getCredential(
request = credentialRequest,
context = context,
)
}.onSuccess {
when (val credential = it.credential) {
is CustomCredential -> {
if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
runCatching {
GoogleIdTokenCredential.createFrom(credential.data)
}.onSuccess { googleIdTokenCredential ->
onSuccessListener(
firebaseAuthWithGoogle(googleIdTokenCredential.idToken)
)
}.onFailure {
Log.e(TAG, "Received an invalid google id token response", it)
}
}
}
}
}.onFailure {
Log.d(TAG, "requestGoogleLogin: ${it.localizedMessage ?: "unknown error"}")
}
}
ui์ ๋ฐ์ดํธ ํ ๋ถ๋ถ์ fragment์์ ์ฃผ์ ๋ฐ๋๋ก ํ๊ณ , ํ์ด์ด๋ฒ ์ด์ค๋ก ๊ตฌ๊ธ ๋ก๊ทธ์ธ์ ํ ๊ฒ์ด๋ฏ๋ก CustomCredential๋ก ์บ์คํ ํด์ค๋ค. ์ด๋ ๊ฒ ์ป์ googleIdToken์ ์ด์ ์ ์ฌ์ฉํ๋ firebaseAuthWithGoogle๋ก ์ ๋ฌํ๋ฉด ๋๋๋ค.
๋ค๋ฅธ ์ฑ๋ค์์ ๋ณผ ์ ์๋ Google ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธ์ผ๋ก ๋ก๊ทธ์ธํ๊ณ ์ถ๋ค๋ฉด ์๋ ์ฝ๋๋ฅผ getCredential์ request์ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค๊ณ ํ๋ค. ์ฌ์ค ์ด ๋ถ๋ถ์ ์ฐจ์ด์ ์ ์ ๋ชจ๋ฅด๊ฒ ๋ค...
val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder()
.setServerClientId(WEB_CLIENT_ID)
.setNonce(<nonce string to use when generating a Google ID token>)
.build()
Credential Manager๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด์ค ํ๋ ค ์ ๋ณด๋ค ํจ์ฌ ๊ฐ๋จํด์ง ๊ฒ ๊ฐ๋ค. client๋ฅผ ์ง์ ๊ด๋ฆฌํ ์ ์๋ค ๋ฟ์ด์ง ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ์ถฉ์คํ๊ณ ํธ๋ฆฌํ๋ค๊ณ ์๊ฐํ๋ค.
๋ก๊ทธ ์์๋ ๊ฐ๋จํ๋ค.
suspend fun logout() {
credentialManager.clearCredentialState(request = ClearCredentialStateRequest())
auth.signOut()
}
๋ก๊ทธ์์์ ์ฌ์ฉ๋๋ clearCredentialState์ญ์ suspend fun์ผ๋ก ์์ฑ๋์ด์๋ค. ๋๋ firebase auth๋ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ๋ก๊ทธ์์์ ํ๋ ค๋ฉด credential, firebase auth ๋ ๋ค ํด์ค์ผํด์ ์ด๋ ๊ฒ ๋ง๋ค์๋ค.
์ธ์๋ก๋ ClearCredentialStateRequest ์ธ์คํด์ค๋ฅผ ๊ทธ๋ฅ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค. ๋ค์ด๊ฐ๋ณด๋ฉด ์๋ฌด ๋ด์ฉ๋ ์๋๋ฐ, ๊ทธ๋ฅ flag์ฉ๋๋ก ์ฌ์ฉํ๋ ์ธ์คํด์ค๋๊ฐ, ๊ณต๊ฐ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋๊น ์ถ์ธกํด๋ณธ๋ค.
๋์์ด ๋๋ค๋ฉด ๋๊ธ์ด๋ ๊ณต๊ฐ ๋ฒํผ ํ ๋ฒ์ฉ ๋๋ฅด๊ณ ๊ฐ์ฃผ์ธ์!
'Android ๐ฅ๏ธ > ์ฝ์งโ๏ธ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Kotlin ํ๊ธ ์ข ์ฑ ๊ตฌ๋ถํ๊ธฐ - ์ฒด์ธ์ ๋ฐ๋ผ ์กฐ์ฌ ๋ค๋ฅด๊ฒ ๋ถ์ด๊ธฐ (0) | 2024.05.24 |
---|---|
์๋ฒ์์ด FCM ๋ณด๋ด๊ธฐ - CloudFunction + FireStore + FCM(2) (0) | 2024.05.19 |
Location Service ์ฝ์งํ๊ธฐ - ๋ด ์์น ๋ฐ์์ค๊ธฐ (0) | 2024.05.03 |
Firebase CloudFunction ์ฝ์ง + FCM(1) (0) | 2024.04.21 |
Firebase Cloud Messaging(FCM) ์ฝ์งํ๊ธฐ (0) | 2024.04.16 |