In today’s world of mobile app development, user authentication is an essential feature for any application that requires personalization or access to user data. Google Sign-in is a popular authentication method that allows users to sign in to an app using their Google account. Jetpack Compose simplifies the process of building user interfaces, when combined with the Model-View-ViewModel (MVVM) clean architecture pattern, it provides a robust and scalable solution for building Android apps. In this article, we will explore the steps required to integrate Google Sign-in into a Jetpack Compose MVVM clean architecture app.

Google Sign-in project configuration

First of all we must configure a Google API Console project. To do that go to configure a project and click on Configure a project.

Click on Select or create a project. If you decide to create a new project the project name will be required.

In our case we are going to create a new project so we enter the name.

Next enter the product name, you can choose whatever name you want. Then click on next and wait for Google to configure your project.

Select Android platform.

Follow the steps indicated in the dialog to get the package name and the SHA-1 signing certificate, complete them and then click on create.

It's done! You just configure Google Sign-in and it's ready to be used in your app. Copy the Client ID, we are going to use this later.

Project structure

As we are using MVVM clean architecture our project is going to be structured in three modules: Presentation, Domain and Data.

Presentation module contains two screens, one for the sign in and a main screen. Once the sign in is successful you will be automatically redirected to the main screen. Also you will find the login view model which handles the login screen state and calls the use cases method.

It's a common practice to send the Google Sign-in token to your backend to validate it and receive an API token in your app, so we have a use case in the domain model that handles this operation.

In the data module you will find the repository implementation that mimics the response from the backend.

Clean Architecture emphasizes the separation of concerns and independence of the various layers in an application. By adhering to these principles, we can ensure that the app’s business logic and core functionality are independent of the user interface. All of this makes it easy to integrate new features like Google Sign-in without affecting other project modules.

The full code is available on this GitHub repository.

Integrating Google Sign-in

In this step we are going to set up our Android Studio project to use Google Sign-in. First thing we need is to add the Google Sign-in dependency in gradle app module:

implementation 'com.google.android.gms:play-services-auth:20.4.1'

Then we need to store the client ID somewhere in the app. In our case we are going to store it as a constant in the dependency injection module:

const val CLIENT_ID = "123456789012-1afghjklo6qvin1a12suhfvj12cq1kb7.apps.googleusercontent.com"

This is a made up client id, if you try to use it the sign in process is going to fail, you have to use the client id generated in your Google Sign-in project configuration.

Also in the dependency injection module we are going to create a function getGoogleSignInClient. This function is used to create and configure a GoogleSignInClient object, which is used to initiate the Google Sign-in flow and get the signed-in account:

fun getGoogleSignInClient(context: Context): GoogleSignInClient {
       val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
           .requestIdToken(CLIENT_ID)
           .build()

       return GoogleSignIn.getClient(context, signInOptions)
   }

single { getGoogleSignInClient(androidContext()) }

Next, we will create the class AuthResultContract. This class specifies the contract for starting a Google Sign-In activity and retrieving the result. It also defines the logic for parsing the result and creating the intent to launch the activity:

class AuthResultContract(private val googleSignInClient: GoogleSignInClient) : 
ActivityResultContract<Int, Task<GoogleSignInAccount>?>() {
   override fun parseResult(resultCode: Int, intent: Intent?): Task<GoogleSignInAccount>? {
       return when (resultCode) {
           Activity.RESULT_OK -> GoogleSignIn.getSignedInAccountFromIntent(intent)
           else -> null
       }
   }

   override fun createIntent(context: Context, input: Int): Intent {
       return googleSignInClient.signInIntent.putExtra("input", input)
   }
}

If you want to go deep into ActivityResultContracts you can visit the Android developers documentation.

Finally in the Login screen we will create the button that launch the Google Sign-in dialog:

@Composable
fun ButtonGoogleSignIn(
   onGoogleSignInCompleted: (String) -> Unit,
   onError: () -> Unit,
   googleSignInClient: GoogleSignInClient = get(),
) {
   val coroutineScope = rememberCoroutineScope()
   val signInRequestCode = 1

   val authResultLauncher =
       rememberLauncherForActivityResult(contract = AuthResultContract(googleSignInClient)) {
           try {
               val account = it?.getResult(ApiException::class.java)
               if (account == null) {
                   onError()
               } else {
                   coroutineScope.launch {
                       onGoogleSignInCompleted(account.idToken!!)
                   }
               }
           } catch (e: ApiException) {
               onError()
           }
       }

   Button(
       onClick = { authResultLauncher.launch(signInRequestCode) },
       modifier = Modifier
           .width(300.dp)
           .height(45.dp),
       shape = RoundedCornerShape(12.dp),
       colors = ButtonDefaults.buttonColors(White),
       elevation = ButtonDefaults.elevation(10.dp)
   ) {
       Row(
           verticalAlignment = Alignment.CenterVertically
       ) {
           Icon(
               painter = painterResource(id = R.drawable.ic_google),
               contentDescription = "Google icon",
               tint = Color.Unspecified,
           )
           Text(
               text = "Access using Google",
               color = Black,
               fontWeight = FontWeight.W600,
               fontSize = 16.sp,
               modifier = Modifier.padding(start = 10.dp)
           )
       }
   }
}

The full code is available on this GitHub repository.

References

https://developers.google.com/identity/sign-in/android/start

https://developer.android.com/training/basics/intents/result