Google SignIn Compose
Tags: android, computerscience, kotlin, programming
Published: 2021-09-24 08:42:34

*In case it helped :)*
<a href="https://www.buymeacoffee.com/aseemwangoo" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Pass Me A Coffee!!" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>

<!-- wp:paragraph -->
<p>We will cover briefly:</p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol><li>Integrating the Google SignIn</li><li>MVVM architecture</li><li>Using Moshi to send data</li><li>(Optional) Check for the previously signed-in user</li></ol>
<!-- /wp:list -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p>Note: This article assumes the reader knows about Jetpack Compose</p></blockquote>
<!-- /wp:quote -->

{% youtube fqVqq8dqTTo %}

<!-- wp:heading {"level":3} -->
<h3>Integrating the Google&nbsp;SignIn</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p><em>Prerequisite: You need to have a project inside the </em><a href="https://cloud.google.com/" rel="noreferrer noopener" target="_blank"><em>GoogleCloudPlatform</em></a><em>.&nbsp;</em></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Before we start integrating, we copy the client id (as received from the Google Cloud Platform) and paste it inside our <strong>res->strings.xml</strong></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>&lt;string name="google_cloud_server_client_id">YOUR_ID&lt;/string></code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":4} -->
<h4>Setup</h4>
<!-- /wp:heading -->

<!-- wp:list -->
<ul><li>Install the dependencies inside <code>build.gradle</code> of your app</li></ul>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>implementation 'com.google.android.gms:play-services-auth:19.2.0'</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>We can now access the methods for signing in with Google. Let’s create a class (<a href="https://github.com/AseemWangoo/hands_on_kotlin/blob/master/app/src/main/java/com/aseemwangoo/handsonkotlin/google/GoogleApiContract.kt" rel="noreferrer noopener" target="_blank"><strong>GoogleApiContract</strong></a>) that is responsible for calling the google APIs</li><li>This class extends from <a href="https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContract" rel="noreferrer noopener" target="_blank"><strong>ActivityResultContract</strong></a><strong> </strong>then we override the method <strong>createIntent</strong></li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>To configure Google Sign-In to request users' ID and basic profile information, create a <code><a href="https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInOptions.Builder#GoogleSignInOptions.Builder%28%29" rel="noreferrer noopener" target="_blank">GoogleSignInOptions</a></code> object with the <code>DEFAULT_SIGN_IN</code> parameter. To request users' email addresses as well, create the <code>GoogleSignInOptions</code> object with the <code>requestEmail</code> option.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>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
}</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>Lastly, we override the method <strong>parseResult</strong></li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>After the user signs in, you can get a <code><a href="https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInAccount" rel="noreferrer noopener" target="_blank">GoogleSignInAccount</a></code> the object for the user in the activity's <code>parseResult</code> method. Otherwise, we return null</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>override fun parseResult(resultCode: Int, intent: Intent?): Task&lt;GoogleSignInAccount>? {
    return when (resultCode) {
        Activity.RESULT_OK -> {
            GoogleSignIn.getSignedInAccountFromIntent(intent)
        }
        else -> null
    }
}</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3>MVVM Architecture</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We follow the MVVM (Model-View-ViewModel) architecture in implementing the next steps</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","width":840,"height":335} -->
<div class="wp-block-image"><figure class="aligncenter is-resized"><img src="https://cdn-images-1.medium.com/max/1600/1*N7deIjwk_FhThXmJ1mx8ow.png" alt="MVVM Architecture" width="840" height="335"/><figcaption>MVVM Architecture</figcaption></figure></div>
<!-- /wp:image -->

<!-- wp:heading {"level":4} -->
<h4>Model (M)</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We create a model aka data class <a href="https://github.com/AseemWangoo/hands_on_kotlin/blob/master/app/src/main/java/com/aseemwangoo/handsonkotlin/google/GoogleUserModel.kt" rel="noreferrer noopener" target="_blank"><strong>GoogleUserModel</strong></a></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>data class GoogleUserModel(
    val name: String?,
    val email: String?
)</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":4} -->
<h4>ViewModel (VM)</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We create a ViewModel class <a href="https://github.com/AseemWangoo/hands_on_kotlin/blob/master/app/src/main/java/com/aseemwangoo/handsonkotlin/google/SignInGoogleViewModel.kt" rel="noreferrer noopener" target="_blank"><strong>SignInGoogleViewModel</strong></a><strong> </strong>which extends <strong>AndroidViewModel</strong></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>class SignInGoogleViewModel(application: Application) : AndroidViewModel(application) {
    private var _userState = MutableLiveData&lt;GoogleUserModel>()
    val googleUser: LiveData&lt;GoogleUserModel> = _userState
    private var _loadingState = MutableLiveData(false)
    val loading: LiveData&lt;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
    }
}</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>We have one <code>LiveData</code> of type <code>GoogleUserModel</code> which listens to changes if it happens on the model class</li><li>The other <code>LiveData</code> is listening to the loading, if the loading state is to be shown or not</li><li>We have a method <code>fetchSignInUser</code> 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.</li></ul>
<!-- /wp:list -->

<!-- wp:heading {"level":4} -->
<h4>View (V)</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We create an <a href="https://github.com/AseemWangoo/hands_on_kotlin/blob/master/app/src/main/java/com/aseemwangoo/handsonkotlin/screens/AuthScreen.kt" rel="noreferrer noopener" target="_blank">authentication screen</a> that comprises our <a href="https://github.com/AseemWangoo/hands_on_kotlin/blob/master/app/src/main/java/com/aseemwangoo/handsonkotlin/components/signingoogle/SignInGoogleComponent.kt" rel="noreferrer noopener" target="_blank">SignInGoogle</a> Compose button.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center"} -->
<div class="wp-block-image"><figure class="aligncenter"><img src="https://cdn-images-1.medium.com/max/1600/1*sSyBe0a9KasObcJ6q4NS-g.png" alt="Auth Screen"/><figcaption>Auth Screen</figcaption></figure></div>
<!-- /wp:image -->

<!-- wp:list -->
<ul><li>Our Auth Screen instantiates the SignInGoogleViewModel and listens to the changes on the GoogleUserModel</li></ul>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>val mSignInViewModel: SignInGoogleViewModel =
    
viewModel(factory = SignInGoogleViewModelFactory(context.applicationContext as Application))
val state = mSignInViewModel.googleUser.observeAsState()
val user = state.value</code></pre>
<!-- /wp:code -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p>Note: We are using the factory of our ViewModel to create an instance.</p></blockquote>
<!-- /wp:quote -->

<!-- wp:paragraph -->
<p>Since our googleUser is of the type <code>LiveData</code> we observe the changes to it and get notified inside here.</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>We create a separate compose component (AuthView) and expose the parameters as</li></ul>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>@Composable
fun AuthView(
    onClick: () -> Unit,
    isError: Boolean = false,
    mSignInViewModel: SignInGoogleViewModel
)</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>This takes in the click action, which gets called on the <a href="https://github.com/AseemWangoo/hands_on_kotlin/blob/master/app/src/main/java/com/aseemwangoo/handsonkotlin/components/signingoogle/SignInGoogleComponent.kt" rel="noreferrer noopener" target="_blank">SignInGoogle</a> component</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>SignInGoogleButton(
    onClick = {
        mSignInViewModel.showLoading()
        onClick()
    },
)</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>This is how the preview of this compose component looks.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center"} -->
<div class="wp-block-image"><figure class="aligncenter"><img src="https://cdn-images-1.medium.com/max/1600/1*HmXFv4ilUiuKa0ZA0wKCJg.png" alt="SignInGoogle Compose Component"/><figcaption>SignInGoogle Compose Component</figcaption></figure></div>
<!-- /wp:image -->

<!-- wp:list -->
<ul><li>Next, we define the click function to be called from Auth Screen. This function should initiate the Google API calls.</li></ul>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>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())
   }
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>We make use of <code><a href="https://developer.android.com/reference/kotlin/androidx/activity/compose/package-summary#rememberLauncherForActivityResult%28androidx.activity.result.contract.ActivityResultContract,kotlin.Function1%29" rel="noreferrer noopener" target="_blank">rememberLauncherForActivityResult</a></code> for launching the GoogleSignIn activity for the result, as specified inside our<code>GoogleApiContract</code></p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>Once we get back the response from our contract, we extract the result using <code>getResult</code> and if the response is not null, we call the view model's <code>fetchSignInUser</code> (This method is responsible for changing the state)</li><li>In case of error, we set the error parameter to true and render the UI accordingly</li><li>Lastly, we pass in the parameters to our AuthView <em>(refer above)</em></li></ul>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>AuthView(
    onClick = { authResultLauncher.launch(signInRequestCode) },
    isError = isError.value,
    mSignInViewModel
)</code></pre>
<!-- /wp:code -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p>Note: <code>authResultLauncher.launch</code>is the function responsible for initiating the Google SignIn API Call</p></blockquote>
<!-- /wp:quote -->

<!-- wp:heading {"level":3} -->
<h3>Using Moshi to send&nbsp;data</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>After the user signs in, we need to show the user’s details on the home screen.</p>
<!-- /wp:paragraph -->

<!-- wp:image -->
<figure class="wp-block-image"><img src="https://cdn-images-1.medium.com/max/1600/1*u9qzNLUaXrb86xb255j3DA.png" alt=""/><figcaption>Using Moshi</figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>To achieve this, we need to pass the data from the authentication screen to the home screen.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>Introducing Moshi</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p><a href="https://github.com/square/moshi" rel="noreferrer noopener" target="_blank">Moshi</a> is a modern JSON library for Android and Java. It makes it easy to parse JSON into Java objects</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>Setup</h4>
<!-- /wp:heading -->

<!-- wp:list -->
<ul><li>Install the dependencies inside <code>build.gradle</code> of your app</li></ul>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>implementation "com.squareup.moshi:moshi:$moshi_version"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":4} -->
<h4>Implement Sending and Receiving data</h4>
<!-- /wp:heading -->

<!-- wp:list -->
<ul><li>We change our route Home to include a <a href="https://github.com/AseemWangoo/hands_on_kotlin/blob/master/app/src/main/java/com/aseemwangoo/handsonkotlin/components/navigation/NavGraph.kt" rel="noreferrer noopener" target="_blank">user</a> parameter</li></ul>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>home/{user}</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>Next, we annotate our data class with <code>generateAdapter</code> and change it to</li></ul>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>@JsonClass(generateAdapter = true)
data class GoogleUserModel(
    @Json(name = "name")
    val name: String?,
    @Json(name = "email")
    val email: String?
)</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>We prepare the data to be sent from <a href="https://github.com/AseemWangoo/hands_on_kotlin/blob/master/app/src/main/java/com/aseemwangoo/handsonkotlin/screens/AuthScreen.kt" rel="noreferrer noopener" target="_blank">Auth Screen</a>. Once we receive the GoogleUserModel, we convert it to JSON using Moshi. Finally, we replace the <code>{user}</code> with the JSON</li></ul>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>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))</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>Finally, for receiving the data on the Home Screen, we extract the arguments received by the <a href="https://github.com/AseemWangoo/hands_on_kotlin/blob/master/app/src/main/java/com/aseemwangoo/handsonkotlin/components/navigation/NavigationComponent.kt" rel="noreferrer noopener" target="_blank">navigation controller</a>.</li></ul>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>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!!)
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The argument is received inside the <a href="https://developer.android.com/reference/android/support/v4/app/FragmentManager.BackStackEntry.html" rel="noreferrer noopener" target="_blank">back stack</a> entry, converted to the GoogleUserModel, and passed as the parameter to the view.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>Check for the previously signed-in user</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>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)</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>We create a new function <code>checkSignedInUser</code> and call it inside the <code>init</code> method of our <a href="https://github.com/AseemWangoo/hands_on_kotlin/blob/master/app/src/main/java/com/aseemwangoo/handsonkotlin/google/SignInGoogleViewModel.kt" rel="noreferrer noopener" target="_blank">ViewModel</a></li></ul>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>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
}</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>In this function, we call the <code>GoogleSignIn.getLastSignedInAccount</code> and set the loading to true</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>If this returns <code>GoogleSignInAccount</code> object (rather than <code>null</code>), the user has already signed in to your app with Google.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>We finally set the loading to false and navigate to the HomeView (from our ViewModel)</li></ul>
<!-- /wp:list -->

{% youtube fqVqq8dqTTo %}

<!-- wp:paragraph -->
<p><a rel="noreferrer noopener" href="https://github.com/AseemWangoo/hands_on_kotlin" target="_blank"><em>Source code.</em></a>

*In case it helped :)*
<a href="https://www.buymeacoffee.com/aseemwangoo" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Pass Me A Coffee!!" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>