Undocumented startActivityForResult behavior for Fragment
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 theintent
together with therequestCode
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 usingsetResult
by sending in theresultCode
andintent
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.
- When we call Fragment’s
startActivityForResult
(note: NOTactivity?.startActivityForResult
), the creation of destination activity is the same. However, the difference is inonActivityResult
- When Android SDK return
onActivityResult
, therequestCode
has been altered. A0xP0000
is append to the originalrequestCode
. - The
P
value starts from 1, and incremented for eachonActivityResult
is called from the same destination fragment. (If another destination Fragment is called, theP
value is reset). - e.g. if the original
requestCode
is 0xFF (i.e. 255), therequestCode
return the first time will be 0x100FF. The second time returnrequestCode
will be 0x200FF. - Upon returning to the source activity’s
onActivityResult
, it is expected that it should callsuper.onActivityResult
, which resides inFragmentActivity
(the parent ofAppCompatActivity
) - The
onActivityResult
inFragmentActivity
will then extract theP
value (usingrequestCode >> 16
). TheP
is the index of the array where the source fragment is stored. (e.g. ifrequestCode
is0x200FF
, then0x200FF >> 16 = 2
) - Using the
P
value, it now has access to the source fragment, which is used in step 9 below. - Now, it strips away the
P
value from therequestCode
(usingrequestCode & 0xffff
), to get back to the original request code (e.g. ifrequestCode
is0x200FF
, then0x200FF & 0xffff = 0xFF
) - As now it gets the original
requestCode
that the source fragment created, it could call the source fragment’sonActivityResult
with the originalrequestCode
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