Jetpack Compose is Android’s modern toolkit for building native UI. It simplifies and accelerates UI development on Android. In this tutorial, we’re going to integrate Google sign-in with our app.
This tutorial assumes that you’ve already set up google credentials (https://console.cloud.google.com/apis/credentials) in the Google Cloud Platform. We’ll mainly focus on the android part of google sign in integration. (Note: You can easily generate SHA1 for your app with gradlew signingReport
command in your IDE, which is required in Google console.)
We’ll start by creating a utility method that will provide GoogleSignInClient
fun getGoogleSignInClient(context: Context): GoogleSignInClient {
val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(context.getString(R.string.backend_client_id)) // Request id token if you intend to verify google user from your backend server
.requestEmail()
.build()
return GoogleSignIn.getClient(context, signInOptions)
}
Next, we’ll create a Contract for ActivtyResultAPI which will get the sign-in result from Google prompt.
class AuthResultContract : ActivityResultContract<Int, Task<GoogleSignInAccount>?>() {
override fun createIntent(context: Context, input: Int?): Intent =
getGoogleSignInClient(context).signInIntent.putExtra("input", input)
override fun parseResult(resultCode: Int, intent: Intent?): Task<GoogleSignInAccount>? {
return when (resultCode) {
Activity.RESULT_OK -> GoogleSignIn.getSignedInAccountFromIntent(intent)
else -> null
}
}
Now, let’s create a composable for the sign in button.
@ExperimentalMaterialApi
@Composable
fun SignInButton(
text: String,
loadingText: String = "Signing in...",
icon: Painter,
isLoading: Boolean = false,
shape: Shape = Shapes.medium,
borderColor: Color = Color.LightGray,
backgroundColor: Color = MaterialTheme.colors.surface,
progressIndicatorColor: Color = MaterialTheme.colors.primary,
onClick: () -> Unit
) {
Surface(
modifier = Modifier.clickable(
enabled = !isLoading,
onClick = onClick
),
shape = shape,
border = BorderStroke(width = 1.dp, color = borderColor),
color = backgroundColor
) {
Row(
modifier = Modifier
.padding(
start = 12.dp,
end = 16.dp,
top = 12.dp,
bottom = 12.dp
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
Icon(
painter = icon,
contentDescription = "SignInButton",
tint = Color.Unspecified
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = if (isLoading) loadingText else text)
if (isLoading) {
Spacer(modifier = Modifier.width(16.dp))
CircularProgressIndicator(
modifier = Modifier
.height(16.dp)
.width(16.dp),
strokeWidth = 2.dp,
color = progressIndicatorColor
)
}
}
}
}
which will create a sign-in button as below
Now, we’ll create AuthView
that contains a sign-in button and error text to display errors that occurred during the sign-in process (if any).
@ExperimentalMaterialApi
@Composable
fun AuthView(
errorText: String?,
onClick: () -> Unit
) {
var isLoading by remember { mutableStateOf(false) }
Scaffold {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
SignInButton(
text = "Sign in with Google",
loadingText = "Signing in...",
isLoading = isLoading,
icon = painterResource(id = R.drawable.ic_google_logo),
onClick = {
isLoading = true
onClick()
}
)
errorText?.let {
isLoading = false
Spacer(modifier = Modifier.height(30.dp))
Text(text = it)
}
}
}
}
Finally, we’ll create AuthScreen
that will launch the google sign in prompt upon clicking on sign in button and navigating to HomeScreen
if sign-in is successful, or show an error if it occurs.
@ExperimentalAnimationApi
@ExperimentalFoundationApi
@ExperimentalCoroutinesApi
@ExperimentalMaterialApi
@Composable
fun AuthScreen(
authViewModel: AuthViewModel
) {
val coroutineScope = rememberCoroutineScope()
var text by remember { mutableStateOf<String?>(null) }
val user by remember(authViewModel) { authViewModel.user }.collectAsState()
val signInRequestCode = 1
val authResultLauncher =
rememberLauncherForActivityResult(contract = AuthResultContract()) { task ->
try {
val account = task?.getResult(ApiException::class.java)
if (account == null) {
text = "Google sign in failed"
} else {
coroutineScope.launch {
authViewModel.signIn(
email = account.email,
displayName = account.displayName,
)
}
}
} catch (e: ApiException) {
text = "Google sign in failed"
}
}
AuthView(
errorText = text,
onClick = {
text = null
authResultLauncher.launch(signInRequestCode)
}
)
user?.let {
HomeScreen(user = it)
}
}
That’s it. The app is ready to handle Google sign-in with compose UI. You can launch and test the Google sign-in flow.
Adding some animations
As you’ll notice, when you click on the sign-in button, a circular loading bar appears directly without animation, which looks boring. We can add smooth animations to make it eye-catching.
@Composable
fun SignInButton(
...
) {
Surface(
...
) {
Row(
modifier = Modifier
.animateContentSize(
animationSpec = tween(
durationMillis = 300,
easing = LinearOutSlowInEasing
)
),
...
) {
...
}
}
}
Follow me if you find this article helpful. You can find the complete code on GitHub.