1

Assume I have 4 fragments A B C and D.

A and B are major fragments, C and D are minor.

I use navigation drawer to switch fragments.

A is the default starting fragment.

I want to achieve following features but cannot figure out how to play with the fragment manager and transactions.

  1. A -> B or B -> A, replace current fragment, do not push backstack, but I want to keep the current fragment status (e.g. list view position) after navigate back
  2. A/B -> C/D, add C/D on top of A/B, using back button to navigate back to A/B.
  3. C -> D or D -> C, replace current fagment
  4. C/D -> A/B, remove current fragment C/D and show A/B

Is the only way to implement this function that I should write some complicated function for switching the fragments (and also need to keep what is current fragment and what is the wanted target fragment)?

Is there better way out?


According to @DeeV 's answer, I came out with something like following.

LocalBrowse and WebsiteExplore are main fragments while Settings and About are sub fragments.

It seems to work fine but still a little bit ugly, any better idea?

private void switchToFragment(Class<?> targetFragmentClz) {
    if(mCurrentFagment!=null && mCurrentFagment.getClass().equals(targetFragmentClz)) {
        return;
    }
    BaseFragment targetFragment = null;
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    if(targetFragmentClz.equals(LocalBrowseFragment.class)
            || targetFragmentClz.equals(WebsiteExploreFragment.class)) {
        if(mCurrentFagment instanceof SettingsFragment //mCurrentFragment will not be null this time
                || mCurrentFagment instanceof AboutFragment) {
            transaction.remove(mCurrentFagment);
        }
        if(mCurrentMainFagment==null || !mCurrentMainFagment.getClass().equals(targetFragmentClz)) {
            targetFragment = (BaseFragment) Fragment.instantiate(this, targetFragmentClz.getName());
            targetFragment.setHasOptionsMenu(true);
            transaction.replace(R.id.ac_content_frame_main, targetFragment);
            mCurrentMainFagment = targetFragment;
        }
    } else {
        targetFragment = (BaseFragment) Fragment.instantiate(this, targetFragmentClz.getName());
        targetFragment.setHasOptionsMenu(true);
        getSupportFragmentManager().popBackStackImmediate();
        transaction.replace(R.id.ac_content_frame_sub, targetFragment)
                   .addToBackStack(null);
    }

    transaction.commit();
    mCurrentFagment = targetFragment;
}
Robin
  • 10,052
  • 6
  • 31
  • 52

1 Answers1

2

One method that I can think of is to stack the two types of fragments on each other. So a system like this:

<FrameLayout>
  <FrameLayout id="main_container">
  <FrameLayout id="sub_container">
<FrameLayout>

Would mean that you have two containers holding fragments. The top one completely covers the other. Thus, you could have two method likes this:

public void swapMainContainer(FragmentManager fm, Fragment frag)
{
   fm.beginTransaction().
   .replace(R.id.main_container, frag, "TAG")
   .commit();
}

public void swapSubContainer(FragmentManager fm, Fragment frag)
{
   fm.popBackstackImmediate();
   fm.beginTransaction()
   .replace(R.id.sub_container, frag, "SUBTAG")
   .addToBackStack(null)
   .commit();
}

So if you use swapMainContainer() only with Fragment A and Fragment B, they will constantly replace each other but the commits won't be added to the backstack.

If you use swapSubContainer() only with Fragment C and Fragment D, they will likewise replace each other, but "Back" will close them. You are also popping the backstack every time you commit a sub Fragment thus removing the previous commit. Though, if there's nothing in the backstack, it won't do anything.

To remove C/D, simply call popBackStack() and it will remove them from the stack.

The flaw in this approach however is if you have more than these two Fragments that are added to the backstack. It may get corrupted.

EDIT:

Regarding saving view state, the fragment itself will have to handle that via this method.

Community
  • 1
  • 1
DeeV
  • 35,865
  • 9
  • 108
  • 95
  • Thanks, but I don't quite follow you. popBackStackImmediate() is a method of FragmentManager, and what is the purpose of calling it here? – Robin Feb 20 '14 at 07:41
  • Oh, sorry. I put it in the wrong place. Updating. – DeeV Feb 20 '14 at 15:37
  • So the purpose of calling the `popBackstack()` is that it will undo the previous `replace` you committed, but only if there's something in the backstack to pop. Since it's immediate in the call, you're not going to accidentally pop the new commit that's going to follow. – DeeV Feb 20 '14 at 15:39
  • Thanks for your reply, I have made a complex function according to your suggestion, updated in the question. It seems to work fine but still a little bit ugly. Am I misunderstanding anything in your answer? – Robin Feb 21 '14 at 07:15
  • Maybe I'm not fully understanding your question. Basically, `replace()` removes a Fragment that is currently in the provided View container and adds the provided new Fragment. If no fragment is there, it merely adds the new one. So `swapMainContainer()` takes care of #1 and `swapSubContainer()` takes care of #3. – DeeV Feb 21 '14 at 17:16
  • `FragmentManager` backstack is a history of `commit()` actions. You can undo the last commit by calling `popBackStackImmediate()` or `popBackStack()`. `popBackStackImmediate()` will undo the last commit immediately so you can do a new commit. In this case a replace of C->D or D->C. Pushing to Backstack also allows user to undo last commit by pressing "Back". This takes care of #2 and #4. – DeeV Feb 21 '14 at 17:19