最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - How to implement Google authenticate with Firebase using Compose Multiplatform - Stack Overflow

programmeradmin0浏览0评论

I tried to implement the Google authentication with Firebase in Compose Multiplatform framework using Kotlin in mobile engineering, but I didn't find the way to build it for iOS. I composed the Google authentication for Android by 'actual' and 'expect' structure. So the part for Android authentication is defined in Android SDK at the Android system level folder. There are three levels: iosMain, commonMain, and androidMain.

I found the way to authenticate with Firebase in androidMain, but didn't find the way for iOS yet. Online tutorials guide me to customize Swift codes directly opening XCode directly with editing iosMain codes but nothing is correct.

I am using Compose Multiplatform framework, not KMP wizard (old version).

I try to build the mobile project for iOS and Android by Kotlin programming language using Compose Multiplatform framework. (New methodology from 2023).

This is commonMain/..../settingsScreen.kt

 Button(             
    onClick = { viewModel.onClickEmailSignIn() },             
    enabled = !isSignedIn,             
    modifier = Modifier.fillMaxWidth(0.8f)         
) { Text("Sign-In with Email") }

This is commonMain/.../SignInViewModel.kt

class SignInViewModel(private val authService: AuthService) {
    private val _isSignedIn = MutableStateFlow(false)
    val isSignedIn: StateFlow<Boolean> get() = _isSignedIn

    fun onClickGoogleSignIn() {
        CoroutineScope(Dispatchers.IO).launch {
            val result = authService.signIn()
            _isSignedIn.value = result?.data != null
        }
    }

    fun onClickEmailSignIn() {
        _isSignedIn.value = true
    }

    fun onClickSignOut() {
        CoroutineScope(Dispatchers.IO).launch {
            authService.signOut()
            _isSignedIn.value = false
        }
    }
}

And this is androidMain/.../AuthService.kt

package com.webatm.paymentapp.core.auth

import android.content.Context
import androidx.credentials.CredentialManager

actual class AuthService(private val context: Context, private val credentialManager: CredentialManager) {
    private val googleAuthClient = GoogleAuthClient(context, credentialManager)

    actual suspend fun signIn(): SignInResult? {
        return googleAuthClient.signIn()
    }

    actual suspend fun signOut() {
        googleAuthClient.signOut()
    }

    actual fun getSignedInUser(): UserData? {
        return googleAuthClient.getSignedInUser()
    }
}

This is the main kernel for Android. GoogleAuthClient.kt

package com.webatm.paymentapp.core.auth

import android.content.Context
import android.content.Intent
import android.provider.Settings
import android.util.Log
import androidx.credentials.CredentialManager
import androidx.credentials.CustomCredential
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetCredentialResponse
import androidx.credentials.exceptions.GetCredentialException
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import com.google.firebase.auth.GoogleAuthProvider
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import com.webatm.paymentapp.R
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.tasks.await
import java.security.SecureRandom
import java.util.Base64

class GoogleAuthClient (
    private val context: Context,
    private val credentialManager: CredentialManager
) {
    private val auth = Firebase.auth

    suspend fun signIn(): SignInResult? {
        val googleIdOption = GetGoogleIdOption.Builder()
            .setFilterByAuthorizedAccounts(false)
            .setServerClientId(context.getString(R.string.default_web_client_id))
            .setAutoSelectEnabled(false)
            .setNonce(generateNonce())
            .build()

        val request = GetCredentialRequest.Builder()
            .addCredentialOption(googleIdOption)
            .build()

        return try {
            val result = credentialManager.getCredential(context, request)
            handleSignIn(result)
            SignInResult(data = getSignedInUser(), errorMessage = null)
        } catch (e: GetCredentialException) {
            e.printStackTrace()
            if (e is androidx.credentials.exceptions.NoCredentialException) {
                promptAddGoogleAccount()
            }
            SignInResult(data = null, errorMessage = e.message)
        }
    }

    private suspend fun handleSignIn(result: GetCredentialResponse) {
        when {
            result.credential is CustomCredential && result.credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL -> {
                try {
                    val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(result.credential.data)
                    val googleIdToken = googleIdTokenCredential.idToken
                    val googleCredentials = GoogleAuthProvider.getCredential(googleIdToken, null)
                    val currentUser = auth.signInWithCredential(googleCredentials).await()
                    if (currentUser != null) {
                        val displayName = currentUser.user?.displayName
                        val email = currentUser.user?.email
                        Log.d("GoogleAuthClient", "User: $displayName, $email")
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                    Log.e("GoogleAuthClient", "Error signing in with Google ID Token: ${e.message}")
                }
            }
            else -> {
                Log.e("GoogleAuthClient", "Unexpected credential type or no credential: ${result.credential?.javaClass}")
                Log.e("GoogleAuthClient", "Credential data: ${result.credential}")
            }
        }
    }

    private fun generateNonce(): String {
        val nonceBytes = ByteArray(16)
        SecureRandom().nextBytes(nonceBytes)

        return Base64.getUrlEncoder().encodeToString(nonceBytes)
    }

    fun getSignedInUser(): UserData? = auth.currentUser?.run {
        UserData(
            userId = uid,
            name = displayName!!,
            photoUrl = photoUrl.toString()
        )
    }

    private fun promptAddGoogleAccount() {
        val intent = Intent(Settings.ACTION_ADD_ACCOUNT)
        intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, arrayOf("com.google"))
        context.startActivity(intent)
    }

    suspend fun signOut() {
        try {
            credentialManager.clearCredentialState(androidx.credentials.ClearCredentialStateRequest())
            auth.signOut()
        } catch (e: Exception) {
            e.printStackTrace()
            if (e is CancellationException) throw e
        }
    }
}

The important thing is that "expect" and "actual" structure. If you know KMP already, you understand what I mean.

But I couldn't find the way to implement Google authentication for iOS. We need to create the iOS API, but we can't access to the iOS system level only using Kotlin. So I think we should customize something using Swift within Xcode environment. How can I do it?

发布评论

评论列表(0)

  1. 暂无评论