7

I am writing a Kotlin app and using Firebase for authentication. As onActivityResult is now depraceted, I am trying to migrate my app to use registerForActivityResult. I have a link to Google account feature, that starts with the Google sign-in flow, as shown here. My code:

    private fun initGoogleSignInClient() =
        activity?.let {

            // Configure Google Sign In
            val gso =
                GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                    .requestIdToken(getString(R.string.default_web_client_id))
                    .requestEmail()
                    .build()

            // Build a GoogleSignInClient with the options specified by gso.
            viewModel.googleSignInClient = GoogleSignIn.getClient(it, gso)
        }

    private fun showLinkWithGoogle() =
        startActivityForResult(viewModel.googleSignInClient.signInIntent, RC_LINK_GOOGLE)

Where initGoogleSignInClient is called in the fragment's onCreateView, and showLinkWithGoogle is called when the user taps the button on the screen. This workes perfectly. I looked for an example using registerForActivityResult, and the best one I found was at the bottom of this page. I added this code:

    private val linkWithGoogle =
        registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
            viewModel.handleGoogleResult(it.data)
        }

    private fun showLinkWithGoogle() =
        linkWithGoogle.launch(IntentSenderRequest.Builder(viewModel.googleSignInClient.signInIntent))

But realized that IntentSenderRequest.Builder needs an IntentSender and not an Intent. I haven't found any example of how to build an IntentSender from an Intent, nor a way to get one from my GoogleSignInClient. Could anyone please provide a full example of using registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult())?

Thank you very much!

Omer Levy
  • 347
  • 4
  • 11

1 Answers1

5

For this use-case, you don't need an ActivityResultContracts of type StartIntentSenderForResult but one of type StartActivityForResult. Here is an example (since you did not provide your full implementation):

Fragment

private val googleRegisterResult =
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            result.checkResultAndExecute {
                val task = GoogleSignIn.getSignedInAccountFromIntent(data)
                val account = task.getResult(ApiException::class.java)
                loginViewModel.onEvent(LoginRegistrationEvent.SignInWithGoogle(account))
            }.onFailure { e -> toast("Error: ${e.message}") }
        }

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     super.onViewCreated(view, savedInstanceState)
     myGoogleSignInButton.setOnClickListener {
        googleRegisterResult.launch(viewModel.googleSignInClient.signInIntent)
    }
}

Then, in your viewmodel, you can handle the login as you would usually do, the only difference is, that you no longer need an RC_SIGN_IN

ViewModel Example

class YourViewModel : ViewModel() {
    fun onEvent(event: LoginRegistrationEvent) {
       when(event) {
         is LoginRegistrationEvent.SignInWithGoogle -> {
              viewModelScope.launch {
                     val credential = GoogleAuthProvider.getCredential(event.account.idToken)
                     Firebase.auth.signInWithCredential(credential).await()
             }
         }
      }
    }
}

To make my life easier, I created an extension function, that checks, if the login was successful and then executes a block of code (in this case, getting the account), while caching any exceptions. Futhermore, inside your block, you have access to an instance of ActivityResult as this:

inline fun ActivityResult.checkResultAndExecute(block: ActivityResult.() -> Unit) =
    if (resultCode == Activity.RESULT_OK) runCatching(block)
    else Result.failure(Exception("Something went wrong"))
Andrew
  • 4,264
  • 1
  • 21
  • 65
  • Ok, I like this solution - it works and it looks clean. Thank you! :) It just makes me wonder why the [New Google Sign-In API doc](https://developers.google.com/identity/sign-in/android/sign-in-identity#handle_sign_in_results) points to the `StartIntentSenderForResult` result contract.... – Omer Levy Jul 13 '21 at 15:42
  • @OmerLevy The docs are most of the time wrong of lead to some dumb solution. Just some google stuff – Andrew Jul 13 '21 at 16:01
  • @Andrew I am learning Android Studio/Kotlin programming, using a class that I discovered has outdated materials. I'm trying to follow your logic here, but I can't figure out one thing. Where does loginViewModel come from? From what I could tell, it's nowhere at develop.android.com. A Google search suggests it's taken from Github. Could you elaborate? – N.Barrett Sep 05 '21 at 02:38
  • @N.Barrett LoginViewModel is just an example, how to integrate `registerForActivityResult` in a MVVM environment. – Andrew Sep 05 '21 at 07:40
  • Could you elaborate, what is the "data" variable in your first code block? – birgersp Apr 29 '22 at 18:20
  • @birgersp It's the ActivityResult from `block: ActivityResult.() -> Unit` – Andrew Apr 30 '22 at 12:41