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?