I'm begginer in Compose Multiplatform and I'm trying to implement Google Sing In (whose objective is to get an GoogleSignInAccount
object, to retrieve the serverAuthCode
value) with Compose Multiplatform.
Here is my UI code :
@Composable
fun MainActivity(colorScheme: ColorScheme, viewModel: MainActivityViewModel) {
val context = getContext()
val launcherManager = getLauncherManager()
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clickable(
onClick = {
viewModel.viewModelScope.launch {
launcherManager?.let {
AndroidGoogleSignInHandler(context, it.getActivityForResult {
}).signIn()
}
}
}
)
.border(width = 1.dp, color = Color.LightGray)
.padding(
start = 12.dp,
end = 16.dp,
top = 12.dp,
bottom = 12.dp
)
.fillMaxWidth()
.height(40.dp)
) {
/*
Icon(
painter = painterResource(id = R.drawable.ic_google_logo),
contentDescription = "Google Login",
tint = Color.Unspecified
)
*/
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Sign in With Google"
)
Spacer(modifier = Modifier.width(16.dp))
}
}
}
The method getLauncherManager()
is an expect
method able to return (or not because of platform incompatibility) the function registerForActivityResult
and looks like this
with AndroidGoogleSignInHandler
and ActivityResultLauncherManager
:
@Composable
expect fun getLauncherManager(): ActivityResultLauncherManager?
expect class AndroidGoogleSignInHandler(activity: Any?, signInLauncher: Any?) : GoogleSignInHandler {
override fun signIn()
override fun signOut()
}
expect class ActivityResultLauncherManager(activity: Any?) {
fun getActivityForResult(callback: (Boolean) -> Unit): Any?
}
You can see that I'm using "Any" eveywhere because iOS doesn't have ActivityResultLauncher<Intent>
type for example
Here are implementation of actual
functions inside androidMain :
actual interface GoogleSignInHandler {
// Add other methods as needed
actual fun signIn()
actual fun signOut()
}
actual class AndroidGoogleSignInHandler actual constructor(val activity: Any?, val signInLauncher: Any?) : GoogleSignInHandler {
private var googleSignInClient: GoogleSignInClient
init {
// Initialize the GoogleSignInClient
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.build()
googleSignInClient = GoogleSignIn.getClient(activity as AppCompatActivity, gso)
}
actual override fun signIn() {
if (activity is Context) {
// Start the sign-in process
val signInIntent = googleSignInClient.signInIntent
(signInLauncher as ActivityResultLauncher<Intent>).launch(signInIntent)
} else {
// Handle invalid activity parameter
}
}
actual override fun signOut() {
// Sign out from Google account
googleSignInClient.signOut()
.addOnCompleteListener { /* Handle sign out completion */ }
}
}
@Composable
actual fun getContext(): Any? {
return LocalContext.current
}
actual class ActivityResultLauncherManager actual constructor(val activity: Any?) {
actual fun getActivityForResult(callback: (Boolean) -> Unit): Any? {
return (activity as? AppCompatActivity)!!.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val signInResult = GoogleSignIn.getSignedInAccountFromIntent(result.data)
val isSuccess = signInResult.isSuccessful
callback(isSuccess)
}
}
}
@Composable
actual fun getLauncherManager(): ActivityResultLauncherManager? {
val context = LocalContext.current as? AppCompatActivity
return remember {
ActivityResultLauncherManager(context!!)
}
You can see in my code that I have a callback callback: (Boolean) -> Unit
that had in parameter the success of the request (Boolean), but as I said in the top of the post, the goal will be to get the serverAuthCode
value so I will replace "Boolean" with "String" if we find the solution ;)
So, in my UI, when I'm trying to call AndroidGoogleSignInHandler(...).signIn(), I'm getting this error :
java.lang.IllegalStateException: LifecycleOwner xxx.xxx.xxx.MainActivity@7dabaee is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.
So I think that I'm implementing registerForActivityResult
too lately, but I'm don't know to deal with that because it's done by expect
and actual
keywords...
Is there a better way to implement GoogleSinIn with Compose Multiplatform ? I searched everywhere and I can't find a solution, except with KMM Jetpack Compose, so without Compose Multiplatform
One solution is to use a WebView, with a login page and this Google login button, then no problem ! But I don't want to deal with that if I can implement natively for both Android AND iOS. My code only deals with Android but if you have the solution for that...
You're trying to convert your android code to be common.
But common code doesn't need to know how each platform does authentication, all it needs is a callback which should be called when button is pressed.
So you can move your platform implementation to a single composable. it can look something like this:
Then in your common code call it like this: