Android startActivityForResult in Fragment Propagate onActivityResult to Both Activity and Result

Management of Sign In Request in Activity

Call startActivityForResult in Fragment

When you call Fragment.startActivityForResult, the result is returned in Fragment.onActivityResult (although Activity.onActivityResult is called, but the requestCode is obfuscated).

You can call activity?.startActivityForResult(intent, MainActivity.REQUEST_SIGN_IN), where the result is returned in Activity.onActivityResult, but the Fragment is not notified.

class TestFragment : Fragment() {    compainion object {        private const val REQUEST_SIGN_IN = 1    }    private fun requestSignIn() {        val intent = ...        // activity?.startActivityForResult(intent, MainActivity.REQUEST_SIGN_IN)        startActivityForResult(intent, REQUEST_SIGN_IN)    }}

Scenario: Management of Sign In Request in Activity

I have multiple fragments which might make a sign in request (startActivityForResult(intent, REQUEST_SIGN_IN)), where I wanted a central code (Activity) to handle signin (handle error, save to database, update UI/state, etc.) and notify the fragment of the successful sign in so that it can run some fragment specific code as well.

Solution 1: Fragment call handleSignIn

I like this solution because it is simple, but fragment must call the handleSignIn function manually.

  • Shared code with Activity via ViewModel
class TestFragment : Fragment() {    compainion object {        private const val REQUEST_SIGN_IN = 1    }    private val viewModel by viewModels<TestViewModel>()    private val mainViewModel by activityViewModels<MainViewModel>()    private fun requestSignIn() {        startActivityForResult(mainViewModel.getAuthIntent(), REQUEST_SIGN_IN)    }    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {        super.onActivityResult(requestCode, resultCode, data)        if (requestCode == REQUEST_SIGN_IN) {            // optional coroutines call, depending on handleSignIn            lifecycleScope.launch(Dispatchers.Default) {                if (mainViewModel.handleSignIn(requestCode, resultCode, data)) {                    // if signin successful                    val auth = FirebaseAuth.getInstance()                    Timber.d("uid=${auth.currentUser?.uid}")                }            }        }    }}

NOTE: For viewModels and activityViewModels, refer to Get ViewModel In Fragment via KTX.

class MainViewModel : ViewModel() {    fun getAuthIntent(): Intent {        val currentUser = FirebaseAuth.getInstance().currentUser        val providers = if (currentUser == null) {            arrayListOf(                AuthUI.IdpConfig.GoogleBuilder().build(),                // uthUI.IdpConfig.EmailBuilder().build(),                AuthUI.IdpConfig.AnonymousBuilder().build()            )        } else {            arrayListOf(                AuthUI.IdpConfig.GoogleBuilder().build()            )        }        return AuthUI.getInstance()            .createSignInIntentBuilder()            .setAvailableProviders(providers)            .enableAnonymousUsersAutoUpgrade()            .build()    }    suspend fun handleSignIn(requestCode: Int, resultCode: Int, data: Intent?): Boolean {        val response = IdpResponse.fromResultIntent(data)        val auth = FirebaseAuth.getInstance()        if (resultCode == Activity.RESULT_OK) {            val user = auth.currentUser            if (user != null) {                Timber.d("name=${user.displayName}, email=${user.email}, uid=${user.uid}")                // TODO: init user            }            return true        }        else {            if (response != null) {                // when the signin credential already signup for this app                // as existing user cannot be linked to existing anonymous user                if (response.error?.errorCode == ErrorCodes.ANONYMOUS_UPGRADE_MERGE_CONFLICT) {                    Timber.w("REQUEST_SIGN_IN conflict detected")                    val currentAnonymousUser = FirebaseAuth.getInstance().currentUser                    // TODO: save anonymous data                    val newCredential = response.credentialForLinking                    if (newCredential != null) {                        val authResult = auth.signInWithCredential(newCredential).await()                        val newUser = auth.currentUser                        // TODO: migrate/merge anonymous data to existing user                        return true                    }                }                else {                    // TODO: toast                    Timber.e("REQUEST_SIGN_IN error", response.error)                    return false                }            }            Timber.w("IdpResponse.fromResultIntent(data) == null")            return false        }    }}

NOTE: Refer Firebase Auth and Firebase Auth Upgrade From Anonymous Account.

Solution 2: Use LiveData to handle communication between Activity and Fragment

This solution is more complex and involved more plumbling

  • Fragment make sign in request via LiveData to Activity
  • Activity handle the sign in request and notify Fragment via LiveData
  • Fragment listen to sign in request result via LiveData (not using onActivityResult)

NOTE: There might be ways to make it simpler, but I didn't explore further.

class TestFragment : Fragment() {    compainion object {        private const val REQUEST_SIGN_IN = 1    }    private val viewModel by viewModels<TestViewModel>()    private val mainViewModel by activityViewModels<MainViewModel>()    private fun requestSignIn() {         mainViewModel.requestSignInEvent.value = Event(Request(requestCode = REQUEST_SIGN_IN, liveData = viewModel.signInEvent))    }    private fun init() {        viewModel.signInEvent.observe(this, Observer { event ->            event.getIfPending()?.also { res ->                if (res.isSuccessful) {                    when(res.data()) {                        REQUEST_SIGN_IN -> {                            val auth = FirebaseAuth.getInstance()                            Timber.d("uid=${auth.currentUser?.uid}")                        }                    }                }            }        })    }}
class TestViewModel : ViewModel() {    internal val signInEvent = MutableLiveData<Event<Resource<Int>>>()}

Activity

class MainActivity : AppCompatActivity() {    companion object {        const val REQUEST_SIGN_IN = 1    }    private val viewModel by viewModels<MainViewModel>()    private fun init() {        viewModel.requestSignInEvent.observe(this, Observer { event ->            event.getIfPending()?.let {                startActivityForResult(getAuthIntent(), REQUEST_SIGN_IN)            }        })    }    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {        super.onActivityResult(requestCode, resultCode, data)        if (requestCode == REQUEST_SIGN_IN) {            val response = IdpResponse.fromResultIntent(data)            val signInRequest = viewModel.requestSignInEvent.value?.peek()            if (resultCode == Activity.RESULT_OK) {                val user = FirebaseAuth.getInstance().currentUser                if (user != null) {                    Timber.d("name=${user.displayName}, email=${user.email}, uid=${user.uid}")                    // TODO: init user                }                signInRequest?.also { request ->                    request.liveData.value = Event(Resource(data = request.requestCode))                }            }            else {                if (response != null)                    // when the signin credential already signup for this app                    // as existing user cannot be linked to existing anonymous user                    if (response.error?.errorCode == ErrorCodes.ANONYMOUS_UPGRADE_MERGE_CONFLICT) {                        Timber.w("REQUEST_SIGN_IN conflict detected")                        val currentAnonymousUser = FirebaseAuth.getInstance().currentUser                        // TODO: save anonymous data                        val newCredential = response.credentialForLinking                        if (newCredential != null) {                            FirebaseAuth.getInstance().signInWithCredential(newCredential)                                .addOnSuccessListener {                                    val newUser = FirebaseAuth.getInstance().currentUser                                    // TODO: migrate/merge anonymous data to existing user                                    signInRequest?.also { request ->                                        request.liveData.value = Event(Resource(data = request.requestCode))                                    }                                }                        }                    }                    else {                        // TODO: toast                        Timber.e("REQUEST_SIGN_IN error", response.error)                        signInRequest?.also { request ->                            request.liveData.value = Event(Resource(exception = response.error!!))                        }                    }                }                else {                    // should not reach here                }            }        }    }    fun getAuthIntent(): Intent {        val currentUser = FirebaseAuth.getInstance().currentUser        val providers = if (currentUser == null) {            arrayListOf(                AuthUI.IdpConfig.GoogleBuilder().build(),                // uthUI.IdpConfig.EmailBuilder().build(),                AuthUI.IdpConfig.AnonymousBuilder().build()            )        } else {            arrayListOf(                AuthUI.IdpConfig.GoogleBuilder().build()            )        }        return AuthUI.getInstance()            .createSignInIntentBuilder()            .setAvailableProviders(providers)            .enableAnonymousUsersAutoUpgrade()            .build()    }}
class MainViewModel : ViewModel() {    internal val requestSignInEvent = MutableLiveData<Event<Request>>()}

NOTE: Refer to Resource (return data or exception) and Event (handle UI event).

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.