Start Activity from Fragment using Transition (API 21 support)
F

4

17

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!

Few answered 18/11, 2014 at 21:48 Comment(0)
U
14

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 :)

Unleash answered 26/2, 2015 at 14:51 Comment(3)
starting an activity for result using the last method you have provided worked for me - thanks a lot for the solution even though I don't find it completely clean, so let's just hope Google doesn't change anything regarding the strange byte shifting.Bags
I guess it would be cleaner to make the call from Activity and then pass the result from the activity to the Fragment but having loads of fragments attached to my activity (because of viewpager) it makes it pretty difficult to handleBags
@user2302510 Exactly, it becomes really confusing to handle that on your own. Glad it helped!Unleash
T
2

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.

Tinea answered 18/11, 2014 at 22:7 Comment(2)
I understand why this is happening, but that doesn't make it any less inconvenient. Is this something that's only absent in the Support library, or can it not be done with native Fragments either? I would hope that they add this feature in the future.Few
Have you tried just calling the Fragment's startActivityForResult(Intent, int, Bundle) method?Tinea
B
0

You can create a listener interface or simply a public function in your fragment and passing the arguments from which you are getting in onActivityResult() of the activity to the listener or the public method of the fragment and do your desired work over there.

Brumley answered 18/11, 2014 at 22:19 Comment(0)
S
0

A simple way:

in Fragment:

 ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation();

 this.startActivityFromFragment(this, intent, requestCode, options);
Selfsatisfied answered 21/7, 2016 at 1:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.