In today’s mobile app development landscape, implementing a smooth and secure authentication process is essential for user engagement and retention. One popular authentication method is Google Sign-In, which allows users to sign in to your app using their Google credentials. In this blog, we will explore how to integrate Google Sign-In seamlessly into your Jetpack Compose UI for Android projects. By following the steps outlined below, you’ll be able to enhance the user experience and streamline the authentication process.
Prerequisites
Before diving into the implementation, ensure that you have a basic understanding of Jetpack Compose and Android development. Familiarity with Kotlin is also beneficial. Additionally, make sure you have set up a project in the Google Cloud Platform (GCP) and obtained the necessary credentials and permissions.
Step 1: Google Sign-In API Adding the Dependency
The first step is to add the required dependency to your project’s build.gradle file. By including the ‘play-services-auth’ library, you gain access to the Google Sign-In API. Make sure to sync the project after adding the dependency to ensure it is correctly imported.
implementation 'com.google.android.gms:play-services-auth:19.2.0'
The version number, in this case, is 19.2.0, which specifies the specific version of the play-services-auth library you want to include.
Step 2: Creating the GoogleUserModel
To handle the user data obtained from the Google Sign-In process, we need to create a data class called ‘GoogleUserModel’. This class will store the relevant user information, such as their name and email address. By encapsulating this data in a model class, we can easily pass it between different components of our app.
data class GoogleUserModel(val name: String?, val email: String?)
Step 3: Implementing the AuthScreen
The ‘AuthScreen’ composable function serves as the entry point for our authentication flow. It interacts with the ‘GoogleSignInViewModel’ and handles the UI components required for the sign-in process. We will create a smooth navigation flow that allows users to initiate the Google Sign-In procedure.
@Composable
fun AuthScreen(
navController: NavController,
) {
val signInRequestCode = 1
// val context = LocalContext.current
/*val mGoogleSignInViewModel: GoogleSignInViewModel = viewModel(
factory = GoogleSignInViewModelFactory(context.applicationContext as Application)
)*/
val mGoogleSignInViewModel: GoogleSignInViewModel = viewModel()
val userState = mGoogleSignInViewModel.googleUser.collectAsState()
val user = userState.value
val authResultLauncher =
rememberLauncherForActivityResult(contract = GoogleApiContract()) { task ->
try {
val gsa = task?.getResult(ApiException::class.java)
if (gsa != null) {
mGoogleSignInViewModel.fetchSignInUser(gsa.email, gsa.displayName)
} else {
mGoogleSignInViewModel.isError(true)
}
} catch (e: ApiException) {
Log.e("Error in AuthScreen%s", e.toString())
}
}
AuthView(onClick = { authResultLauncher.launch(signInRequestCode) }, mGoogleSignInViewModel)
// Strange issue after upgrading to latest version
if (mGoogleSignInViewModel.googleUser.collectAsState().value.name != "") {
LaunchedEffect(key1 = Unit) {
mGoogleSignInViewModel.hideLoading()
// GoogleUserModel(user.name, user.email)
val userJson =
MoshiUtils.getJsonAdapter(GoogleUserModel::class.java).lenient().toJson(user)
navController.navigate(Screen.Settings.passGoogleUserData(userJson)) {
popUpTo(route = Screen.Auth.route) { inclusive = true }
}
}
}
}
Step 4: Designing the AuthView
In the ‘AuthView’ composable function, we will define the visual layout of our authentication screen. This includes displaying a loading indicator, the Google Sign-In button, and handling potential error messages. By providing a user-friendly interface, we enhance the overall user experience.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun AuthView(onClick: () -> Unit, mGoogleSignInViewModel: GoogleSignInViewModel) {
Scaffold {
it.calculateTopPadding()
if (
mGoogleSignInViewModel.loading.collectAsState().value == true &&
!mGoogleSignInViewModel.loading.collectAsState().value
) {
FullScreenLoaderComponent()
} else {
Column(
modifier = Modifier.fillMaxSize().padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Spacer(modifier = Modifier.weight(1F))
Image(
painterResource(id = R.mipmap.ic_launcher),
contentDescription = stringResource(R.string.app_desc),
)
Spacer(modifier = Modifier.weight(1F))
SignInGoogleButton(
onClick = {
mGoogleSignInViewModel.showLoading()
onClick()
}
)
Spacer(modifier = Modifier.weight(1F))
Text(
text = APP_SLOGAN,
textAlign = TextAlign.Center,
)
when {
mGoogleSignInViewModel.loading.collectAsState().value -> {
/*isError.let {*/
Text(AUTH_ERROR_MSG, style = MaterialTheme.typography.bodyLarge)
mGoogleSignInViewModel.hideLoading()
// }
}
}
}
}
}
}
@Preview(name = "Day Mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Composable
fun PreviewAuthView() {
ResumeSenderTheme { AuthScreen(navController = rememberNavController()) }
}
Code Breakdown
- The AuthScreen function is a Composable function that represents the authentication screen. It takes a NavController as a parameter, which will be used for navigating to different screens.
- Inside the AuthScreen function, an instance of GoogleSignInViewModel is created using the viewModel function. This ViewModel is responsible for managing the authentication state related to Google Sign-In.
- The userState variable collects the state of the googleUser property from the GoogleSignInViewModel as a Composable state. This allows the UI to update reactively whenever the user state changes.
- The AuthView composable function is called to display the UI components of the authentication screen. It takes a lambda function onClick and the mGoogleSignInViewModel as parameters.
- After calling the AuthView composable, there is a check to see if the user’s name is not empty. If it’s not empty, a LaunchedEffect is used to perform an action. It hides the loading state, converts the user object to JSON using MoshiUtils, and navigates to the settings screen, passing the user data along.
- The AuthView composable function is responsible for rendering the UI of the authentication screen. It uses the Scaffold composable to set up the basic layout structure.
- Inside the AuthView composable, there’s a Column that contains various UI components of the authentication screen, such as an Image, a sign-in button (SignInGoogleButton), and a Text displaying the app’s slogan.
- The when expression is used to handle different states. In this case, when the loading state of the mGoogleSignInViewModel is true, it displays an error message (AUTH_ERROR_MSG) using the Text composable.
- Finally, there are @Preview annotations for previewing the AuthView composable in different UI modes (day mode and night mode).
Step 5: Managing Authentication with GoogleSignInViewModel
The ‘GoogleSignInViewModel’ class plays a crucial role in managing the authentication state and communicating with the Google Sign-In API. Depending on your preference, you can choose to use LiveData or StateFlow to update the user’s sign-in status and handle loading and error states. This ViewModel acts as a bridge between the UI and the underlying authentication logic.
/*
* It contains commented code I think it will helpful when implement logout functionality
* in future thats why kept as it is here.
* */
class GoogleSignInViewModel : ViewModel() {
private var _userState = MutableStateFlow(GoogleUserModel("", ""))
val googleUser = _userState.asStateFlow()
private var _loadingState = MutableStateFlow(false)
val loading = _loadingState.asStateFlow()
private val _errorStateFlow = MutableStateFlow(false)
val errorStateFlow = _errorStateFlow.asStateFlow()
/* init {
checkSignedInUser(application.applicationContext)
}*/
fun fetchSignInUser(email: String?, name: String?) {
_loadingState.value = true
viewModelScope.launch {
_userState.value =
GoogleUserModel(
email = email,
name = name,
)
}
_loadingState.value = false
}
/* private fun checkSignedInUser(applicationContext: Context) {
_loadingState.value = true
val gsa = GoogleSignIn.getLastSignedInAccount(applicationContext)
if (gsa != null) {
_userState.value = GoogleUserModel(
email = gsa.email,
name = gsa.displayName,
)
}
_loadingState.value = false
}*/
fun hideLoading() {
_loadingState.value = false
}
fun showLoading() {
_loadingState.value = true
}
fun isError(isError: Boolean) {
_errorStateFlow.value = isError
}
}
/*class GoogleSignInViewModelFactory(
private val application: Application
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
if (modelClass.isAssignableFrom(GoogleSignInViewModel::class.java)) {
return GoogleSignInViewModel(application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}*/
Conclusion
By following this tutorial, you have learned how to seamlessly integrate Google Sign-In into your Jetpack Compose UI for Android. The integration allows users to sign in to your app using their Google credentials, enhancing the user experience and streamlining the authentication process. By leveraging the power of Jetpack Compose and the Google Sign-In API, you can build secure and user-friendly apps that cater to the modern authentication needs of your users.