DEV Community

Cover image for Google SignIn Compose
aseem wangoo
aseem wangoo

Posted on

Google SignIn Compose

In case it helped :)
Pass Me A Coffee!!

We will cover briefly:

  1. Integrating the Google SignIn
  2. MVVM architecture
  3. Using Moshi to send data
  4. (Optional) Check for the previously signed-in user

Note: This article assumes the reader knows about Jetpack Compose

Integrating the Google SignIn

Prerequisite: You need to have a project inside the GoogleCloudPlatform

Before we start integrating, we copy the client id (as received from the Google Cloud Platform) and paste it inside our res->strings.xml

<string name="google_cloud_server_client_id">YOUR_ID</string>

Setup

  • Install the dependencies inside build.gradle of your app
implementation 'com.google.android.gms:play-services-auth:19.2.0'
  • We can now access the methods for signing in with Google. Let’s create a class (GoogleApiContract) that is responsible for calling the google APIs
  • This class extends from ActivityResultContract then we override the method createIntent

To configure Google Sign-In to request users' ID and basic profile information, create a GoogleSignInOptions object with the DEFAULT_SIGN_IN parameter. To request users' email addresses as well, create the GoogleSignInOptions object with the requestEmail option.

override fun createIntent(context: Context, input: Int?): Intent {
 GoogleSignInOptions gso = new     GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken("YOUR_ID_HERE")
        .requestEmail()
        .build();
 val intent =  GoogleSignIn.getClient(context,gso)
 return intent.signInIntent
}
  • Lastly, we override the method parseResult

After the user signs in, you can get a GoogleSignInAccount the object for the user in the activity's parseResult method. Otherwise, we return null

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

MVVM Architecture

We follow the MVVM (Model-View-ViewModel) architecture in implementing the next steps

MVVM Architecture
MVVM Architecture

Model (M)

We create a model aka data class GoogleUserModel

data class GoogleUserModel(
    val name: String?,
    val email: String?
)

ViewModel (VM)

We create a ViewModel class SignInGoogleViewModel which extends AndroidViewModel

class SignInGoogleViewModel(application: Application) : AndroidViewModel(application) {
    private var _userState = MutableLiveData<GoogleUserModel>()
    val googleUser: LiveData<GoogleUserModel> = _userState
    private var _loadingState = MutableLiveData(false)
    val loading: LiveData<Boolean> = _loadingState
    fun fetchSignInUser(email: String?, name: String?) {
      _loadingState.value = true
      viewModelScope.launch {
          _userState.value =
            GoogleUserModel(
              email = email,
              name = name,
            )
       }
      _loadingState.value = false
    }
    fun hideLoading() {
      _loadingState.value = false
    }
    fun showLoading() {
      _loadingState.value = true
    }
}
  • We have one LiveData of type GoogleUserModel which listens to changes if it happens on the model class
  • The other LiveData is listening to the loading, if the loading state is to be shown or not
  • We have a method fetchSignInUser which is responsible for changing the state of our model class. We mark the loading state as true, when the change is about to start, and set it to false when it's done.

View (V)

We create an authentication screen that comprises our SignInGoogle Compose button.

Auth Screen
Auth Screen

  • Our Auth Screen instantiates the SignInGoogleViewModel and listens to the changes on the GoogleUserModel
val mSignInViewModel: SignInGoogleViewModel =
    
viewModel(factory = SignInGoogleViewModelFactory(context.applicationContext as Application))
val state = mSignInViewModel.googleUser.observeAsState()
val user = state.value

Note: We are using the factory of our ViewModel to create an instance.

Since our googleUser is of the type LiveData we observe the changes to it and get notified inside here.

  • We create a separate compose component (AuthView) and expose the parameters as
@Composable
fun AuthView(
    onClick: () -> Unit,
    isError: Boolean = false,
    mSignInViewModel: SignInGoogleViewModel
)

This takes in the click action, which gets called on the SignInGoogle component

SignInGoogleButton(
    onClick = {
        mSignInViewModel.showLoading()
        onClick()
    },
)

This is how the preview of this compose component looks.

SignInGoogle Compose Component
SignInGoogle Compose Component

  • Next, we define the click function to be called from Auth Screen. This function should initiate the Google API calls.
val authResultLauncher =
  rememberLauncherForActivityResult(contract = GoogleApiContract()) { task ->
   try {
     val gsa = task?.getResult(ApiException::class.java)
     if (gsa != null) {
        mSignInViewModel.fetchSignInUser(gsa.email, gsa.displayName)
     } else {
        isError.value = true
     }
   } catch (e: ApiException) {
        Timber.d("Error in AuthScreen%s", e.toString())
   }
}

We make use of rememberLauncherForActivityResult for launching the GoogleSignIn activity for the result, as specified inside ourGoogleApiContract

  • Once we get back the response from our contract, we extract the result using getResult and if the response is not null, we call the view model's fetchSignInUser (This method is responsible for changing the state)
  • In case of error, we set the error parameter to true and render the UI accordingly
  • Lastly, we pass in the parameters to our AuthView (refer above)
AuthView(
    onClick = { authResultLauncher.launch(signInRequestCode) },
    isError = isError.value,
    mSignInViewModel
)

Note: authResultLauncher.launchis the function responsible for initiating the Google SignIn API Call

Using Moshi to send data

After the user signs in, we need to show the user’s details on the home screen.

Using Moshi

To achieve this, we need to pass the data from the authentication screen to the home screen.

Introducing Moshi

Moshi is a modern JSON library for Android and Java. It makes it easy to parse JSON into Java objects

Setup

  • Install the dependencies inside build.gradle of your app
implementation "com.squareup.moshi:moshi:$moshi_version"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"

Implement Sending and Receiving data

  • We change our route Home to include a user parameter
home/{user}
  • Next, we annotate our data class with generateAdapter and change it to
@JsonClass(generateAdapter = true)
data class GoogleUserModel(
    @Json(name = "name")
    val name: String?,
    @Json(name = "email")
    val email: String?
)
  • We prepare the data to be sent from Auth Screen. Once we receive the GoogleUserModel, we convert it to JSON using Moshi. Finally, we replace the {user} with the JSON
val moshi = Moshi.Builder().build()
val jsonAdapter=moshi.adapter(GoogleUserModel::class.java).lenient()
val userJson = jsonAdapter.toJson(user)
navController.navigate(Destinations.Home.replace("{user}",userJson))
  • Finally, for receiving the data on the Home Screen, we extract the arguments received by the navigation controller.
composable(Destinations.Home) { backStackEntry ->
  val userJson = backStackEntry.arguments?.getString("user")
  val moshi = Moshi.Builder().build()
  val jsonAdapter = moshi.adapter(GoogleUserModel::class.java)
  val userObject = jsonAdapter.fromJson(userJson!!)
  HomeView(navController, userModel = userObject!!)
}

The argument is received inside the back stack entry, converted to the GoogleUserModel, and passed as the parameter to the view.

Check for the previously signed-in user

Once the user signs in for the first time, we should not show them the sign-in screen again (after they come back to our app)

  • We create a new function checkSignedInUser and call it inside the init method of our ViewModel
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
}
  • In this function, we call the GoogleSignIn.getLastSignedInAccount and set the loading to true

If this returns GoogleSignInAccount object (rather than null), the user has already signed in to your app with Google. 

  • We finally set the loading to false and navigate to the HomeView (from our ViewModel)

Source code.

In case it helped :)
Pass Me A Coffee!!

Discussion (1)

Collapse
vishnuharidas profile image
Vishnu Haridas

Good article!

Tip: code is hard to learn without syntax coloring. Start your code block with the language name, and they will highlight it for you, for example:



   ```kotlin
   // code
   ```