DarkLeafyGreen
DarkLeafyGreen

Reputation: 70406

Show dialog above view pager that has nested fragments

I have setup a very simple test project https://github.com/ArtworkAD/ViewPagerDialogTest to evaluate following situation: the main activity has a view pager which hosts a single fragment using support fragment manager:

public class MainActivity extends AppCompatActivity {

    // ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
        // ...
        tabLayout.setupWithViewPager(viewPager);
    }

    @Override
    protected void onResume() {
        super.onResume();

        MainActivity.CustomDialog dialog = (MainActivity.CustomDialog) getSupportFragmentManager().findFragmentByTag(MainActivity.CustomDialog.TAG);

        if (dialog == null) {
            new MainActivity.CustomDialog().show(getSupportFragmentManager().beginTransaction(), MainActivity.CustomDialog.TAG);
        }
    }
    // ...
}

When the activity is resumed a dialog fragment is shown inside the main activity.

The single fragment inside the view pager is defined like this:

public class RootFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.root_fragment, container, false);
        if (savedInstanceState == null) {
            getFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();
        }
        return root;
    }
}

This root fragment allows us to stack other fragments on the "root_frame". So we stack another and another:

public class FirstLevelFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        setRetainInstance(true);
        View root = inflater.inflate(R.layout.first_level_fragment, container, false);
        root.findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SecondLevelFragment f = (SecondLevelFragment) getActivity().getSupportFragmentManager().findFragmentByTag("NESTED");
                if (f == null) {
                    getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
                }
            }
        });
        return root;
    }

    public static class SecondLevelFragment extends Fragment {

        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            setRetainInstance(true);
            return inflater.inflate(R.layout.second_level_fragment, container, false);
        }
    }
}

This works great! The stacking idea is taken from https://stackoverflow.com/a/21453571/401025 . However when dialog is shown and the users goes to the second level fragment and rotates the screen I get following exception:

E/AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{de.azzoft.viewpagerdialogtest/de.azzoft.viewpagerdialogtest.MainActivity}: java.lang.IllegalArgumentException: No view found for id 0x7f0c0083 (de.azzoft.viewpagerdialogtest:id/root_frame) for fragment SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}

E/AndroidRuntime: Caused by: java.lang.IllegalArgumentException: No view found for id 0x7f0c0083 (de.azzoft.viewpagerdialogtest:id/root_frame) for fragment SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}

Full stack trace: https://github.com/ArtworkAD/ViewPagerDialogTest/blob/master/README.md

Without the dialog appearing everything works great. You can test it by downloading the test project.

It seems that the dialog, which is actually a fragment, messes up fragment hierarchy when it is added to the activity. Any ideas how to fix this?

It is important that the second fragment is retained.

Upvotes: 3

Views: 1754

Answers (5)

Libin
Libin

Reputation: 17085

No view found for id 0x7f0c0083 (de.azzoft.viewpagerdialogtest:id/root_frame) for fragment SecondLevelFragment

When Activity recreates on rotate, the Activity FragmentManger tries to add the SecondLevelFragment into R.id.root_frame . But the root_frame view is not in Activity layout, its in FirstLevelFragment layout. Thats why the app crashes.

You have to make two changes to fix this issue.

Add the FirstLevelFragment into the RootFragment using the getChildFragmentManager

getChildFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();

Add the SecondLevelFragment using FragmentManager

getFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();

Finally remove the setRetainInstance from FirstLevelFragment and SecondLevelFragment as nested fragments doesn't required to set retain.

If you need to pop back the SecondLevelFragment on back press you need to pass the back press the event to RootFragment and pop from back stack.

Override the back press on activity

@Override
public void onBackPressed() {
    Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.viewpager);
    if(fragment instanceof RootFragment){
        boolean handled = ((RootFragment)fragment).onBackPressed();
        if(handled){
            return;
        }
    }
    super.onBackPressed();
}

And handle the back press on RootFragment

public boolean onBackPressed() {
    int count = getChildFragmentManager().getBackStackEntryCount();
    if(count > 0){
        getChildFragmentManager().popBackStackImmediate();
        return true;
    }
    return false;
} 

I created a Pull request to your repository . please check https://github.com/ArtworkAD/ViewPagerDialogTest/pull/1

Let me know if any questions.

Upvotes: 2

Hardik Chauhan
Hardik Chauhan

Reputation: 2746

I think you missed setContentView() in onCreate() of your Activity. See your Fragment can not be added without a View hierarchy. Your Fragment is hosted by an activity. So you need to set the content to the activity first.

Hope this Helps, Thanks.

Upvotes: 0

Mohammad Hossein Gerami
Mohammad Hossein Gerami

Reputation: 1388

If you override onDismiss so resolved crash. enjoy it.

 @Override
 protected void onResume() {
        super.onResume();

        DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag(TAG);

        if(dialog == null){
            CustomDialog.newInstance().show(getSupportFragmentManager(), TAG);

        }


    }


   public static class CustomDialog extends DialogFragment {


        public static CustomDialog newInstance() {
            CustomDialog d = new CustomDialog();
            return d;
        }

        @Override
        public void onDismiss(DialogInterface dialog) {
//            super.onDismiss(dialog);
            Toast.makeText(getActivity(), "onDismiss", Toast.LENGTH_LONG).show();
        }

        @Override
        public void onCancel(DialogInterface dialog) {
//            super.onCancel(dialog);

            Toast.makeText(getActivity(), "onCancel", Toast.LENGTH_LONG).show();

        }

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            setRetainInstance(true);

        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            builder.setTitle("Dialog");
            builder.setMessage("This is a message!");

            builder.setPositiveButton("Okay", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();

                }
            });

            builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();

                }
            });

            return builder.show();
        }
    }

Upvotes: 1

Murtaza Khursheed Hussain
Murtaza Khursheed Hussain

Reputation: 15336

Well, I had downloaded your Test app and it seems that I have fixed the problem.

In your FirstLevelFragment class, comment the following line

 //if (nestedNestedFragment == null) {
     getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
 //}

And

Comment setRetainInstance(true); in SecondLevelFragment

Upvotes: 0

bonnyz
bonnyz

Reputation: 13548

If you want to keep the state of your Fragments you should use a FragmentStatePagerAdapter.

From the docs:

Implementation of PagerAdapter that uses a Fragment to manage each page. This class also handles saving and restoring of fragment's state.

If you use this you can also remove the setRetainInstance(true) calls.

Upvotes: 0

Related Questions