onActivityResult() not called in new nested fragment API
Asked Answered
A

7

80

I have been using the new nested fragment API that Android includes in the support library.

The problem that I am facing with nested fragments is that, if a nested fragment (that is, a fragment that has been added to another fragment via the FragmentManagerreturned by getChildFragmentManager()) calls startActivityForResult(), the nested fragment's onActivityResult() method is not called. However, both the parent fragment's onActivityResult() and activity's onActivityResult() do get called.

I don't know if I am missing something about nested fragments, but I did not expect the described behavior. Below is the code that reproduces this problem. I would very much appreciate if someone can point me in the right direction and explain to me what I am doing wrong:

package com.example.nestedfragmentactivityresult;

import android.media.RingtoneManager;
import android.os.Bundle;
import android.content.Intent;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends FragmentActivity
{
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        this.getSupportFragmentManager()
            .beginTransaction()
            .add(android.R.id.content, new ContainerFragment())
            .commit();
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        super.onActivityResult(requestCode, resultCode, data);

        // This is called
        Toast.makeText(getApplication(),
            "Consumed by activity",
            Toast.LENGTH_SHORT).show();
    }

    public static class ContainerFragment extends Fragment
    {
        public final View onCreateView(LayoutInflater inflater,
                                       ViewGroup container,
                                       Bundle savedInstanceState)
        {
            View result = inflater.inflate(R.layout.test_nested_fragment_container,
                container,
                false);

            return result;
        }

        public void onActivityCreated(Bundle savedInstanceState)
        {
            super.onActivityCreated(savedInstanceState);
            getChildFragmentManager().beginTransaction()
                .add(R.id.content, new NestedFragment())
                .commit();
        }

        public void onActivityResult(int requestCode,
                                     int resultCode,
                                     Intent data)
        {
            super.onActivityResult(requestCode, resultCode, data);

            // This is called
            Toast.makeText(getActivity(),
                "Consumed by parent fragment",
                Toast.LENGTH_SHORT).show();
        }
    }

    public static class NestedFragment extends Fragment
    {
        public final View onCreateView(LayoutInflater inflater,
                                       ViewGroup container,
                                       Bundle savedInstanceState)
        {
            Button button = new Button(getActivity());
            button.setText("Click me!");
            button.setOnClickListener(new View.OnClickListener()
            {
                public void onClick(View v)
                {
                    Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
                    startActivityForResult(intent, 0);
                }
            });

            return button;
        }

        public void onActivityResult(int requestCode,
                                     int resultCode,
                                     Intent data)
        {
            super.onActivityResult(requestCode, resultCode, data);

            // This is NOT called
            Toast.makeText(getActivity(),
                "Consumed by nested fragment",
                Toast.LENGTH_SHORT).show();
        }
    }
}

test_nested_fragment_container.xml is:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

</FrameLayout>
Aleppo answered 27/11, 2012 at 8:28 Comment(1)
Found the same bug a week ago. Had to manually call onActivityResult() on the nested Fragment. Personally, I think it's a bug. Haven't actually checked to see if it's doable from the framework, but I think it might be. They just need to check all the child fragments for the top level fragment and call onActivityResult() on each of them. If there isn't a way to do this from within the framework, then ok, that's that. But if it is, I seriously consider this a bug.Mortgagee
K
58

Yes, the onActivityResult() in nested fragment will not be invoked by this way.

The calling sequence of onActivityResult (in Android support library) is

  1. Activity.dispatchActivityResult().
  2. FragmentActivity.onActivityResult().
  3. Fragment.onActivityResult().

In the 3rd step, the fragment is found in the FragmentMananger of parent Activity. So in your example, it is the container fragment that is found to dispatch onActivityResult(), nested fragment could never receive the event.

I think you have to implement your own dispatch in ContainerFragment.onActivityResult(), find the nested fragment and invoke pass the result and data to it.

Kufic answered 27/11, 2012 at 15:33 Comment(8)
Ok, so the question is... is this an intended behavior, or is it some kind of bug? To me it's at least counter intuitive that the nested fragment does not receive the activity's result.Aleppo
Ok.. You can consider it as a bug, since neither the android support library nor the native code of version 4.2 could dispatch the result to nested fragment. Try to solve this by yourself, not very hard...Kufic
Well, I guess that's what I'll do. I tried to avoid this in order not to introduce more coupling between the hosting activity and its fragments.Aleppo
There's a bug filed against it: code.google.com/p/android/issues/detail?id=40537Maseru
Seems this bug just not been fixed yet...?Noncontributory
check this post, there is problem description and common workaround solution: shomeser.blogspot.com/2014/01/nested-fragments-for-result.htmlModest
Seems this bug still hasn't been fixed with sdk 22. One of my nested fragments receive the onActivityResult() but the second doesn't...Unthrone
Fixed since 23.2 support lib.Couturier
E
145

I solved this problem with the following code (support library is used):

In container fragment override onActivityResult in this way:

@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        List<Fragment> fragments = getChildFragmentManager().getFragments();
        if (fragments != null) {
            for (Fragment fragment : fragments) {
                fragment.onActivityResult(requestCode, resultCode, data);
            }
        }
    }

Now nested fragment will receive call to onActivityResult method.

Also, as noted Eric Brynsvold in similar question, nested fragment should start activity using it's parent fragment and not the simple startActivityForResult() call. So, in nested fragment start activity with:

getParentFragment().startActivityForResult(intent, requestCode);
Ectype answered 12/12, 2013 at 12:13 Comment(9)
Thanks +100000 for getParentFragment()!!, but onActivityResult implementation giving error!! paste.ubuntu.com/6565045 but implementing same sort of like Mr.Whizzle(one of the ans) thing in activity worked for me!Myoglobin
FYI I'm using pagertabstrip with viewpager in firstfragment and loading many child fragment in viewpagerMyoglobin
Stacktrace says that NullPoinerException occurs somewhere in your FirstFragment.onActivityResult() method. I think, you can put breakpoint in this method and figure out which line produce this exception and why this happens.Ectype
This is a great answer. Not only it's easy and straightforward, it solves the whole lower 16-bit problem with nested fragments and activity results. Nice!Unfortunate
That last line should be the first :-)Deplume
this is the only solution which works in my case after searching for 3 hours..I am working on the slide menu with tab in bottom and all in fragment..I wish ,I could mark +5000 points for this awsome trick... :)Ellene
Thanks a million! The getParentFragment() solved it all!Eupatrid
This should be the accepted answer. A slightly more [perhaps overly paranoidly] robust answer can be found at gist.github.com/artem-zinnatullin/6916740.Zaidazailer
Works, thanks! In my case, the list of fragments of child-fragment manager can contain null references, so a check against not null was necessary.Squad
K
58

Yes, the onActivityResult() in nested fragment will not be invoked by this way.

The calling sequence of onActivityResult (in Android support library) is

  1. Activity.dispatchActivityResult().
  2. FragmentActivity.onActivityResult().
  3. Fragment.onActivityResult().

In the 3rd step, the fragment is found in the FragmentMananger of parent Activity. So in your example, it is the container fragment that is found to dispatch onActivityResult(), nested fragment could never receive the event.

I think you have to implement your own dispatch in ContainerFragment.onActivityResult(), find the nested fragment and invoke pass the result and data to it.

Kufic answered 27/11, 2012 at 15:33 Comment(8)
Ok, so the question is... is this an intended behavior, or is it some kind of bug? To me it's at least counter intuitive that the nested fragment does not receive the activity's result.Aleppo
Ok.. You can consider it as a bug, since neither the android support library nor the native code of version 4.2 could dispatch the result to nested fragment. Try to solve this by yourself, not very hard...Kufic
Well, I guess that's what I'll do. I tried to avoid this in order not to introduce more coupling between the hosting activity and its fragments.Aleppo
There's a bug filed against it: code.google.com/p/android/issues/detail?id=40537Maseru
Seems this bug just not been fixed yet...?Noncontributory
check this post, there is problem description and common workaround solution: shomeser.blogspot.com/2014/01/nested-fragments-for-result.htmlModest
Seems this bug still hasn't been fixed with sdk 22. One of my nested fragments receive the onActivityResult() but the second doesn't...Unthrone
Fixed since 23.2 support lib.Couturier
F
8

Here's how I solved it.

In Activity:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    List<Fragment> frags = getSupportFragmentManager().getFragments();
    if (frags != null) {
        for (Fragment f : frags) {
            if (f != null)
                handleResult(f, requestCode, resultCode, data);
        }
    }
}

private void handleResult(Fragment frag, int requestCode, int resultCode, Intent data) {
    if (frag instanceof IHandleActivityResult) { // custom interface with no signitures
        frag.onActivityResult(requestCode, resultCode, data);
    }
    List<Fragment> frags = frag.getChildFragmentManager().getFragments();
    if (frags != null) {
        for (Fragment f : frags) {
            if (f != null)
                handleResult(f, requestCode, resultCode, data);
        }
    }
}
Fawkes answered 16/11, 2013 at 0:32 Comment(2)
Thanks for your trick ! but can you explain what is IHandleActivityResult interface in code by editing your answer ? +1Myoglobin
IHandleActivityResult is just an empty interface. Fragments that want the handleResult passed to it can implement the empty interface. It's unneeded but I found it useful so that not all fragments were getting the callback.Fawkes
S
7

For Androidx with Navigation Components using NavHostFragment

Updated answer on September 2, 2019

This issue is back in Androidx, when you are using a single activity and you have nested fragment inside the NavHostFragment, onActivityResult() is not called for the child fragment of NavHostFragment.

To fix this, you need manually route the call to the onActivityResult() of child fragments from onActivityResult() of the host activity.

Here's how to do it using Kotlin code. This goes inside the onActivityResult() of your main activity that hosts the NavHostFragment:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    val navHostFragment = supportFragmentManager.findFragmentById(R.id.your_nav_host_fragment)
    val childFragments = navHostFragment?.childFragmentManager?.fragments
    childFragments?.forEach { it.onActivityResult(requestCode, resultCode, data) }
}

This will make sure that the onActivityResult() of the child fragments will be called normally.

For Android Support Library

Old answer

This problem has been fixed in Android Support Library 23.2 onwards. We don't have to do anything extra anymore with Fragment in Android Support Library 23.2+. onActivityResult() is now called in the nested Fragment as expected.

I have just tested it using the Android Support Library 23.2.1 and it works. Finally I can have cleaner code!

So, the solution is to start using the latest Android Support Library.

Sternmost answered 26/3, 2016 at 18:51 Comment(2)
can you please share any official link for this?Eason
@RaviRupareliya I have updated the answer with the link.Sternmost
F
4

I have search the FragmentActivity source.I find these facts.

  1. when fragment call startActivityForResult(), it actually call his parent FragmentActivity's startAcitivityFromFragment().
  2. when fragment start activity for result,the requestCode must below 65536(16bit).
  3. in FragmentActivity's startActivityFromFragment(),it call Activity.startActivityForResult(), this function also need a requestCode,but this requestCode is not equal to the origin requestCode.
  4. actually the requestCode in FragmentActivity has two part. the higher 16bit value is (the fragment's index in FragmentManager) + 1.the lower 16bit is equal to the origin requestCode. that's why the requestCode must below 65536.

watch the code in FragmentActivity.

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));
}

when result back.FragmentActivity override the onActivityResult function,and check if the requestCode's higher 16bit is not 0,than pass the onActivityResult to his children fragment.

    @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    mFragments.noteStateNotSaved();
    int index = requestCode>>16;
    if (index != 0) {
        index--;
        final int activeFragmentsCount = mFragments.getActiveFragmentsCount();
        if (activeFragmentsCount == 0 || index < 0 || index >= activeFragmentsCount) {
            Log.w(TAG, "Activity result fragment index out of range: 0x"
                    + Integer.toHexString(requestCode));
            return;
        }
        final List<Fragment> activeFragments =
                mFragments.getActiveFragments(new ArrayList<Fragment>(activeFragmentsCount));
        Fragment frag = activeFragments.get(index);
        if (frag == null) {
            Log.w(TAG, "Activity result no fragment exists for index: 0x"
                    + Integer.toHexString(requestCode));
        } else {
            frag.onActivityResult(requestCode&0xffff, resultCode, data);
        }
        return;
    }

    super.onActivityResult(requestCode, resultCode, data);
}

so that's how FragmentActivity dispatch onActivityResult event.

when your have a fragmentActivity, it contains fragment1, and fragment1 contains fragment2. So onActivityResult can only pass to fragment1.

And than I find a way to solve this problem. First create a NestedFragment extend Fragment override the startActivityForResult function.

public abstract class NestedFragment extends Fragment {

@Override
public void startActivityForResult(Intent intent, int requestCode) {

    List<Fragment> fragments = getFragmentManager().getFragments();
    int index = fragments.indexOf(this);
    if (index >= 0) {
        if (getParentFragment() != null) {
            requestCode = ((index + 1) << 8) + requestCode;
            getParentFragment().startActivityForResult(intent, requestCode);
        } else {
            super.startActivityForResult(intent, requestCode);
        }

    }
}

}

than create MyFragment extend Fragment override onActivityResult function.

public abstract class MyFragment extends Fragment {

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    int index = requestCode >> 8;
    if (index > 0) {
        //from nested fragment
        index--;

        List<Fragment> fragments = getChildFragmentManager().getFragments();
        if (fragments != null && fragments.size() > index) {
            fragments.get(index).onActivityResult(requestCode & 0x00ff, resultCode, data);
        }
    }
}

}

That's all! Usage:

  1. fragment1 extend MyFragment.
  2. fragment2 extend NestedFragment.
  3. No more different.Just call startActivityForResult() on fragment2, and override the onActivityResult() function.

Waring!!!!!!!! The requestCode must bellow 512(8bit),because we spilt the requestCode in 3 part

16bit | 8bit | 8bit

the higher 16bit Android already used,the middle 8bit is to help fragment1 find the fragment2 in his fragmentManager array.the lowest 8bit is the origin requestCode.

Forfeit answered 6/7, 2016 at 9:50 Comment(0)
D
0

For Main Activity you write OnActivityForResult with Super class like as

  @Override
public void onActivityResult(int requestCode, int resultCode, Intent result) {
    super.onActivityResult(requestCode, resultCode, result);
    if (requestCode == Crop.REQUEST_PICK && resultCode == RESULT_OK) {
        beginCrop(result.getData());
    } }

For activity Write intent without getActivity() like as

 Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts"));
        pickContactIntent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE); // Show user only contacts w/ phone numbers
       startActivityForResult(pickContactIntent, 103);

OnActivityResult for fragment

   @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == 101 && resultCode == getActivity().RESULT_OK && null != data) {

}}

Disaccustom answered 2/6, 2016 at 10:6 Comment(0)
C
-2

I faced the same problem! I solved it by using static ChildFragment in the ParentFragment, like this:

private static ChildFragment mChildFragment;

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    mChildFragment.onActivityResult(int requestCode, int resultCode, Intent data);

}
Chinatown answered 9/12, 2013 at 16:31 Comment(1)
mChildFragment is unnecessarily static.Deflower

© 2022 - 2024 — McMap. All rights reserved.