Sunday 12 September 2021

Undocumented startActivityForResult behavior for Fragment

  No comments

Everyone knows startActivityForResult and onActivityResult as clearly documented by Google in Getting Result from an Activity and Activity.

However, there’s hardly any mention of how it behaves when Fragment is involved. And due to this, this causes some common bugs on various apps, and hard to understand behavior.

Check out and ensure you are clear about it, that could save previous future time debugging on the strange issue (that I faced in the past 😓)

The basic startActivityForResult Activity flow

Before we get into the undocumented behavior, let’s look at the known behavior. You could skip to the other section if you were already familiar with it.

The entire startActivityForResult, and onActivityResult is to allow a 2-way communication between the source activity and the destination activity. Its flow is as shown in the diagram below.





  • The source activity call, startActivityForResult by sending in the intent together with the requestCode to Android SDK.
  • Android SDK then opens the activity accordingly as stated in the Intent.
  • Once the destination activity has finish with its job, it returns to its caller activity. It could send the result back using setResult by sending in the resultCode and intent

Several notes here:

  • The resultCode is used by the destination activity to flag to its source activity what its status (e.g. OK, Cancel, etc).
  • The requestCode is used by the source activity to know which destination activity is returning the call.
  • We could see that the destination activity has no visibility of requestCode

Undocumented startActivityForResult Fragment Flow

In Fragment, we also have startActivityForResult function to call, and also onActivityResult function to override.

This raises several questions…e.g. How does it works? Is it the same as activity’s startActivityForResult and onActivityResult? How are they related?

The short answer is, they are similar but subtly different. A frequent cause of confusion, and bug. So it’s important to understand the similarity and differences.

The fragment flow illustrated.

Below is the diagram the illustrates clearly how it all works together. The differences with activity’s startActivityForResult and onActivityResult flow is colored in RED.




Let me elaborate on it.

  1. When we call Fragment’s startActivityForResult (note: NOT activity?.startActivityForResult), the creation of destination activity is the same. However, the difference is in onActivityResult
  2. When Android SDK return onActivityResult , the requestCode has been altered. A 0xP0000 is append to the original requestCode.
  3. The P value starts from 1, and incremented for each onActivityResult is called from the same destination fragment. (If another destination Fragment is called, the P value is reset).
  4. e.g. if the original requestCode is 0xFF (i.e. 255), the requestCode return the first time will be 0x100FF. The second time return requestCode will be 0x200FF.
  5. Upon returning to the source activity’s onActivityResult, it is expected that it should call super.onActivityResult, which resides in FragmentActivity (the parent of AppCompatActivity)
  6. The onActivityResult in FragmentActivity will then extract the P value (using requestCode >> 16). The P is the index of the array where the source fragment is stored. (e.g. if requestCode is 0x200FF, then 0x200FF >> 16 = 2)
  7. Using the P value, it now has access to the source fragment, which is used in step 9 below.
  8. Now, it strips away the P value from the requestCode (using requestCode & 0xffff), to get back to the original request code (e.g. if requestCode is 0x200FF, then 0x200FF & 0xffff = 0xFF)
  9. As now it gets the original requestCode that the source fragment created, it could call the source fragment’s onActivityResult with the original requestCode

What do we learn from the flow?

A. The behavior of Activity’s startActivityForResult is different from Fragment’s startActivityForResult

In Fragment, if we call activity?.startActivityForResult(...), it will end up in flow like in diagram 1 above. Hence Fragment’s onActivityResult will not be called automatically.

B. If we override activity’s onActivityResult, make sure we have super.onActivityResult in place

If we override source activity’s onActivityResult, but forget to have super.onActivityResult there, step 5 above will fail, and the source Fragment’s onActivityResult will not be called.

C. The requestCode seen in activity’s onActivityResult is different from the one fragment provided

Due to the reason, Android SDK appended P to the requestCode, in the activity’s onActivityResult, we’ll never get the identical requestCode there. This at times causes confusion to the developer when debugging the code there.

D. When an activity is restored from the state (i.e. onStateInstance != null), it should avoid recreating its fragment.

As stated in step 7 above, when onActivityResult is called, it tried to retrieve the original fragment. This is also performed for the activity that is restored from a killed state (e.g. emulated using Don’t Keep Activity).

So if the developer recreates the Fragment every time onCreate is called (regardless if it is a restored state or not), then the original Fragment that was restored will be destroyed, and onActivityResult of the caller-fragment will mysteriously not be called.

This is a pitfall even for many experienced developers and app released in the field, as it is a very hard-to-identify bug (since it is not happening every time, and require a more complex flow to trigger).

No comments :

Post a Comment

Loading...