Android 4.2: back stack behaviour with nested fragments

后端 未结 17 1723
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-11-29 16:01

With Android 4.2, the support library got support for nested fragments see here. I\'ve played around with it and found an interesting behaviour / bug regarding back stack an

相关标签:
17条回答
  • 2020-11-29 16:29

    The real answer to this question is in the Fragment Transaction's function called setPrimaryNavigationFragment.

    /**
     * Set a currently active fragment in this FragmentManager as the primary navigation fragment.
     *
     * <p>The primary navigation fragment's
     * {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
     * to process delegated navigation actions such as {@link FragmentManager#popBackStack()}
     * if no ID or transaction name is provided to pop to. Navigation operations outside of the
     * fragment system may choose to delegate those actions to the primary navigation fragment
     * as returned by {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
     *
     * <p>The fragment provided must currently be added to the FragmentManager to be set as
     * a primary navigation fragment, or previously added as part of this transaction.</p>
     *
     * @param fragment the fragment to set as the primary navigation fragment
     * @return the same FragmentTransaction instance
     */
    public abstract FragmentTransaction setPrimaryNavigationFragment(Fragment fragment);
    

    You have to set this function on the initial parent fragment when the activity is adding it. I have a replaceFragment function inside of my activity that looks like this:

    public void replaceFragment(int containerId, BaseFragment fragment, boolean addToBackstack) {
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.setPrimaryNavigationFragment(fragment);
        if (addToBackstack) {
            fragmentTransaction.addToBackStack(fragment.TAG);
        }
    
        fragmentTransaction.replace(containerId, fragment).commit();
    }
    

    This gives you the same behavior as if your clicking back from regular Fragment B back to Fragment A, except now it is on the child fragments as well!

    0 讨论(0)
  • 2020-11-29 16:30

    I solved this issue by keeping currently opened fragment in property of activity. Then I override method

     // INSIDE ACTIVITY
     override fun onBackPressed() {
        val fragment = currentFragment
    
        if(fragment != null && fragment.childFragmentManager.fragments.any()){
            fragment.childFragmentManager.popBackStack()
        }
        else {
            super.onBackPressed()
        }
    }
    

    This is how I add child fragment into currently opened fragment from within itself.

     // INSIDE FRAGMENT
     menu_fragment_view.setBackgroundColor(-((Math.random() * 10000 ).toInt() % 30000)) // to see change
     add_fragment_button.setOnClickListener {
            val transaction = childFragmentManager.beginTransaction()
    
            transaction.add(R.id.menu_fragment_fragment_holder, MenuFragment())
    
            transaction.addToBackStack(null)
    
            transaction.commit()
        }
    

    This is xml layout of parent fragment and added fragment

     <?xml version="1.0" encoding="utf-8"?>
     <androidx.constraintlayout.widget.ConstraintLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:app="http://schemas.android.com/apk/res-auto"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:id="@+id/menu_fragment_view">
         <Button
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintLeft_toLeftOf="parent"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:id="@+id/add_fragment_button"
             android:text="Just add fragment"/>
         <FrameLayout
             android:id="@+id/menu_fragment_fragment_holder"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintLeft_toLeftOf="parent"/> 
    </androidx.constraintlayout.widget.ConstraintLayout>
    
    0 讨论(0)
  • 2020-11-29 16:32

    If you have a DialogFragment which in turn has nested fragments, the 'workaround' is a bit different. Instead of setting an onKeyListener to the rootView, you'll need to do that with the Dialog. Also you will be setting up a DialogInterface.OnKeyListener and not the View one. Of course, remember addToBackStack!

    Btw, having 1 fragment on the backstack for delegating the call back to the activity is my personal use case. Typical scenarios might be for the count to be 0.

    Here's what you gotta do within the onCreateDialog

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            Dialog dialog =  super.onCreateDialog(savedInstanceState);
            dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
                @Override
                public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
                    if(keyCode == KeyEvent.KEYCODE_BACK){
                        FragmentManager cfm = getChildFragmentManager();
                        if(cfm.getBackStackEntryCount()>1){
                            cfm.popBackStack();
                            return true;
                        }   
                    }   
                    return false;
                }
            });
            return dialog;
        }
    
    0 讨论(0)
  • 2020-11-29 16:35

    This solution may be better version of @ismailarilik answer:

    Nested Fragment version

    private boolean onBackPressed(FragmentManager fm) {
        if (fm != null) {
            if (fm.getBackStackEntryCount() > 0) {
                fm.popBackStack();
                return true;
            }
    
            List<Fragment> fragList = fm.getFragments();
            if (fragList != null && fragList.size() > 0) {
                for (Fragment frag : fragList) {
                    if (frag == null) {
                        continue;
                    }
                    if (frag.isVisible()) {
                        if (onBackPressed(frag.getChildFragmentManager())) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }
    
    @Override
    public void onBackPressed() {
        FragmentManager fm = getSupportFragmentManager();
        if (onBackPressed(fm)) {
            return;
        }
        super.onBackPressed();
    }
    
    0 讨论(0)
  • 2020-11-29 16:35

    This solution may be better, bacause it checks all the levels of nested fragments:

     /**
     * This method will go check all the back stacks of the added fragments and their nested fragments
     * to the the {@code FragmentManager} passed as parameter.
     * If there is a fragment and the back stack of this fragment is not empty,
     * then emulate 'onBackPressed' behaviour, because in default, it is not working.
     *
     * @param fm the fragment manager to which we will try to dispatch the back pressed event.
     * @return {@code true} if the onBackPressed event was consumed by a child fragment, otherwise {@code false}.
     */
    public static boolean dispatchOnBackPressedToFragments(FragmentManager fm) {
    
        List<Fragment> fragments = fm.getFragments();
        boolean result;
        if (fragments != null && !fragments.isEmpty()) {
            for (Fragment frag : fragments) {
                if (frag != null && frag.isAdded() && frag.getChildFragmentManager() != null) {
                    // go to the next level of child fragments.
                    result = dispatchOnBackPressedToFragments(frag.getChildFragmentManager());
                    if (result) return true;
                }
            }
        }
    
        // if the back stack is not empty then we pop the last transaction.
        if (fm.getBackStackEntryCount() > 0) {
            fm.popBackStack();
            fm.executePendingTransactions();
            return true;
        }
    
        return false;
    }
    

    in your activity onBackPressed you can simply call it this way:

    FragmentManager fm = getSupportFragmentManager();
                    // if there is a fragment and the back stack of this fragment is not empty,
                    // then emulate 'onBackPressed' behaviour, because in default, it is not working
                    if (!dispatchOnBackPressedToFragments(fm)) {
                        // if no child fragment consumed the onBackPressed event,
                        // we execute the default behaviour.
                        super.onBackPressed();
                    }
    
    0 讨论(0)
  • 2020-11-29 16:36

    for ChildFragments this works..

    @Override
        public void onBackPressed() {
    
     if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
                getSupportFragmentManager().popBackStack();
            } else {
                doExit(); //super.onBackPressed();
            }
    }
    
    0 讨论(0)
提交回复
热议问题