Solution for startActivityForResult() deprecated in Fragment 1.3.0

Beginning in Fragment 1.3.0, the Fragment::startActivityForResult() method is deprecated. Below is a walkthrough porting the new implementation to CriminalIntent. The documentation provides an example and some background. Note that while this post walks through the replacement for a Fragment starting an Activity for a response, this is also the same process that should be used going forward for an Activity to start an Activity for a response.

https://developer.android.com/training/basics/intents/result

The onClick listener for the crimeSuspectButton needs to start the already created activity. Since we expect the user to pick a contact, we need to get the result of the contact they chose. Let’s stub out the onClick listener first, then we’ll build the contract to launch the intent for a result.

crimeSuspectButton.apply {
    setOnClickListener {
        // Launch Intent
    }
}

When we wish to launch an intent for an activity and we expect to receive a result from that activity, we need to register ourselves with the OS as an ActivityResultListener, similarly to how we do with fragments and the FragmentManager. This needs to be done in the event while the target activity is running our process & activity are destroyed due to low memory. When our activity, and in turn our fragment, is recreated we’ll have to alert the ActivityManager we are awaiting that result they are holding.

This is done in three pieces. Create the following three private data members on the CrimeFragment class and then we’ll make each one momentarily and explain the steps.

private lateinit var pickContactContract: ActivityResultContract<Uri, Uri?>
private lateinit var pickContactCallback: ActivityResultCallback<Uri?>
private lateinit var pickContactLauncher: ActivityResultLauncher<Uri>

All three abstract classes are templated and may look confusing since every type is Uri. But they relate as follows:

• ActivityResultContract<InputType, OutputType>
• ActivityResultCallback<OutputType>
• ActivityResultLauncher<InputType>

We’ll instantiate each of the three inside of onCreate(). The ActivityResultContract is responsible for creating and packaging the intent to start the target activity. It is also responsible for receiving and verifying the result. We’ll use an object expression to instantiate the contract.

pickContactContract = object : ActivityResultContract<Uri, Uri?>() {
    override fun createIntent(context: Context, input: Uri): Intent {
        Log.d(TAG, “createIntent() called”)
        return Intent(Intent.ACTION_PICK, input)
    }
    override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
        Log.d(LOG_TAG, “parseResult() called”)
        if(resultCode != Activity.RESULT_OK || intent == null)
            return null
        return intent.data
    }
}

The createIntent() method is making a generic pick implicit intent, similarly to how we did back prior to resolving the intent to enable/disable the button. The parseResult() method has some of the information we previously saw in Activity::onActivityResult(). Namely, the resultCode and the actual intent result. This is where we’ll validate we got a proper response. Notice that there is no requestCode like we previously had. This contract object is the entity that connects the request with the result.

If parseResult() contains part of the previous Activity::onActivityResult() method, the validating part, then we need to actually handle the result after the result has been unpacked and the data received. The ActivityResultCallback is called upon return of the result to now handle the data that was unpacked. The ActivityResultCallback class has an abstract function called onActivityResult(), but it only receives the output data. While the name is the same as we have seen, its usage is different. ActivityResultCallback is a SAM (single abstract method) class so we will instantiate it with a lamba function.

pickContactCallback = ActivityRequestCallback<Uri?> { contactUri: Uri? ->
    Log.d(LOG_TAG, “onActivityResult() called with result: $result”)
    // handle the actual result later to query the database for the contact
}

The last step is to create the launcher to handle the request and the result . This is the step that actually registers our fragment with the ActivityManager as the listener. For this step, we simply bundle the contract with the associated callback.

pickContactLauncher = registerForActivityResult(pickContactContract, pickContactCallback)

We’re now ready to do the launching. We first will patch where we were testing if the intent could be resolved. We’ll use the contract to create a pick intent and send it the data of what we would like to pick.

val pickContactIntent = pickContactContract.createIntent(requireContext(), ContactsContract.Contacts.CONTENT_URI)

And now, in the onClick listener for our suspect button we will finally launch the intent by providing the data we wish to pick and start the activity.

crimeSuspectButton.apply {
    setOnClickListener {
        pickContactLauncher.launch(ContactsContract.Contacts.CONTENT_URI)
    }
}

At this point, we can run the app and we are able to select a suspect! (Make sure your device has at least one contact created. This is especially important if you are running an emulator).

2 Likes

May I just add for clarity, onActivityResult is now deprecated, and the code that in the book goes inside that function, now goes inside the ActivityRequestCallback (from val queryFields onward)

one problem I am facing here is then when I don’t select the contact then it going to kill the app so if anyone has come up with this then help, please

hi, can you please write this for chapter 6 too. because the StartActivityForResult() was used in chapter 6 too . and i can’t understand what to do with android document

Is it possible that there is a typo here? Should ActivityRequestCallback be ActivityResultCallback instead?

How are you doing the query?

I mean, I’m doing something like this:

        val cursor = contactUri?.let { requireActivity().contentResolver.query(it, queryFields, null, null, null) }

is it possible that, instead of checking if contactUri has any information you are just telling Android that this info should be there, doing something like this:

        val cursor = requireActivity().contentResolver.query(contactUri!!, queryFields, null, null, null)

If not maybe you can show me what did you put on pickContactCallback