I'm trying to port an Android app to the new support library (support-v4:21.0.0) and I'm having trouble starting Activities from Fragments with a transition.
In my Activities, I've been doing something like:
Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation(this).toBundle();
ActivityCompat.startActivityForResult(this, intent, REQUEST_SOMETHING, options);
which works fine for Activities. However, if I try to do something similar with Fragments, like:
Activity activity = getActivity();
Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity).toBundle();
ActivityCompat.startActivityForResult(activity, intent, REQUEST_SOMETHING, options);
it turns out that onActivityResult()
is not called for the Fragment, but only the enclosing Activity. I haven't found anything in the support library to pass the options Bundle as a parameter to startActivityForResult()
on an actual Fragment and have it call back to onActivityResult()
in that Fragment. Is this possible?
The simplest solution would be to handle all onActivityResult()
calls in the Activity itself, but I'd rather not do that because I have a ton of possible Fragments that may be receiving that callback.
Help is appreciated. Thanks!
Sadly, ActivityCompat.startActivityForResult()
doesn't work quite right in Fragments
(see Alex Lockwood's answer). For several weeks I marvelled at how Google never gave us an ActivityCompat
method equivalent to Fragment's implementation of startActivityForResult()
. What were they thinking?! But then I had an idea: Let's take a look at how the method is actually implemented.
As a matter of fact, startActivityForResult()
in Fragment is different from the one in Activity (see here):
public void startActivityForResult(Intent intent, int requestCode) {
if (mActivity == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
mActivity.startActivityFromFragment(this, intent, requestCode);
}
Now startActivityFromFragment()
looks like this (see here):
public void startActivityFromFragment(Fragment fragment, Intent intent,
int requestCode) {
if (requestCode == -1) {
super.startActivityForResult(intent, -1);
return;
}
if ((requestCode&0xffff0000) != 0) {
throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
}
super.startActivityForResult(intent,
((fragment.mIndex + 1) << 16) + (requestCode & 0xffff));
}
Google uses some odd byte shifting on the request code to make sure only the calling Fragment's onActivityResult()
is called afterwards. Now since ActivityCompat
doesn't provide any startActivityFromFragment()
, the only option left is to implement it yourself. Reflection is required to access the package private field mIndex
.
public static void startActivityForResult(Fragment fragment, Intent intent,
int requestCode, Bundle options) {
if (Build.VERSION.SDK_INT >= 16) {
if ((requestCode & 0xffff0000) != 0) {
throw new IllegalArgumentException("Can only use lower 16 bits" +
" for requestCode");
}
if (requestCode != -1) {
try {
Field mIndex = Fragment.class.getDeclaredField("mIndex");
mIndex.setAccessible(true);
requestCode = ((mIndex.getInt(this) + 1) << 16) + (requestCode & 0xffff);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
ActivityCompat.startActivityForResult(fragment.getActivity(), intent,
requestCode, options);
} else {
fragment.getActivity().startActivityFromFragment(this, intent, requestCode);
}
}
Copy that method anywhere you like and use it from your Fragment. Its onActivityResult()
will be called as it should.
UPDATE:
Support library v23.2 was released and it seems startActivityFromFragment(Fragment fragment, Intent intent, int requestCode, Bundle options)
does the job now :)
The ActivityCompat#startActivityForResult()
method is just a proxy for the activity's startActivityForResult(Intent, Bundle)
method. Calling the method from inside a fragment class doesn't mean that the Fragment
's onActivityResult()
will eventually be called as I'm sure you've found out. The framework has know way of knowing from which class the call originated... the only correct behavior would be to call the Activity
's onActivityResult()
method in this case.
It sounds like the best option would be to handle everything in the activity's onActivityResult()
method as you suggested in your post.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With