Search code examples
androidonactivityresult

Android seems to be ignoring my setResult call for onActivityResult


I have 2 apps, one a very simple toy app that exists to call the other:

const val AUTHENTICATE_CODE = 42

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)

        fab.setOnClickListener {
            Intent(Intent.ACTION_VIEW, Uri.parse("testapp://hello.world/")) //2nd app has intent filter to intercept this.
                .also { intent -> startActivityForResult(intent, AUTHENTICATE_CODE) }

        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        val textView = findViewById<TextView>(R.id.hello_text)

        if (requestCode == AUTHENTICATE_CODE && resultCode == Activity.RESULT_OK) {
            requireNotNull(data) {
                textView.text = "Error: Intent was null. Data lost."
                return@onActivityResult
            }
        val dataExtra = data.getStringExtra("com.example.app.DATA")
        requireNotNull(dataExtra){
            textView.text = "Error: Intent did not contain data."
            return@onActivityResult
        }
            Log.d("TestAppPlsIgnore", "Result Intent received")
            textView.text = "Success! $dataExtra"
        } else {
            textView.text = "Something went wrong. Request = $requestCode; Result = $resultCode"
        }
    }
//...
}

The other app is a little more involved:

  • The activity in app 2 that the toy app launches implements the navigation library from Jetpack.
  • Most of the fragments that are in that activity's nav graph implement the same ViewModel. i.e. private val mainViewModel by activityViewModels<MainActivityViewModel>()
  • Inside the MainActivityViewModel is a LiveData<String> that we'll call data. The MainActivity of app 2 has an observer watching data similar to this:

    val dataObserver = Observer<String> { data ->
        val result = Intent()
        result.putExtra("com.example.app.DATA", data)
        Log.d("MainActivity.DataObserver", "Sending data $data")
        setResult(Activity.RESULT_OK, result)
        finish()
    }
    mainViewModel.data.observe(this, dataObserver)
    
  • In the general flow to get to a point where a string is put into data, the navigation view of the main activity will likely navigate between one or more fragments.

The expected result: When a string is added to data in app 2, the observer will create the result intent, set it as the result, and finish app 2. App 1 will receive the result and call onActivityResult, and we should display "Success!" plus some data.

What I get: The observer does work. The log statement shows the correct data was received by the observer. App 2 finishes. And app 1's onActivityResult displays the fail case, showing the correct request code, but a response code == Activity.RESULT_CANCELLED. If the requireNotNull(data) statement is moved outside the if statement, app 1 will instead show that the intent returned was null.

My questions:

  1. RESULT_CANCELLED is not being explicitly returned, and I am attempting to return an intent with data. So that should only leave the activity crashing as a reason why RESULT_CANCELLED is being returned. Navigating across a nav graph will inevitably cause some fragments to reach the end of their lifecycle. Would Android confuse that for an activity crashing?
  2. Why is there a null intent when onActivityResult is being called? For the most part, I'm just following what's outlined in the documentation, if a bit more verbosely.
  3. Is this not the right way to send a simple string between two specific apps? I don't want to use share intents, because this is meant to be a more direct communication between specific apps rather than a broad communication between my app and a category of apps.

Solution

  • It turns out that you should not call finishAfterTransition() elsewhere in an app if you plan to use an Observer-based setup like mine to send data through startActivityForResult(). finishAfterTransition() causes a conflict with any calls to finish(), and you'll send a null result and a ResultCode of RESULT_CANCELLED.