Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transaction between fragments only inside one ActionBar Tab

I have an app with three tabs (ActionBar Tabs), each one with one fragment at a time.

TabListener

TabsActivity

Tab1 -> ListFragment1 -> ListFragment2 -> Fragment3

Tab2 -> Tab2Fragment

Tab3 -> Tab3Fragment

The problem is when I create the FragmentTransaction (inside OnListItemClicked) from ListFragment1 to ListFragment2, the fragments inside the other tabs also change to ListFragment2.

In the end, I want to change fragments only inside on tab and preserve the state of the other tabs.

I'm already saving the state (OnSavedInstance()). Do you know what I'm missing here?

Some of the code:

public class TabsActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tabs);

        // setup Action Bar for tabs
        ActionBar actionBar = getActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        // instantiate fragment for the tab
        Fragment networksFragment = new NetworksFragment();
        // add a new tab and set its title text and tab listener
        actionBar.addTab(actionBar.newTab().setText("Tab1")
                .setTabListener(new TabsListener(ListFragment1)));

        // instantiate fragment for the tab
        Fragment historyFragment = new HistoryFragment();
        // add a new tab and set its title text and tab listener
        actionBar.addTab(actionBar.newTab().setText("Tab2")
                .setTabListener(new TabsListener(Tab2Fragment)));

        // instantiate fragment for the tab
        Fragment settingsFragment = new SettingsFragment();
        // add a new tab and set its title text and tab listener
        actionBar.addTab(actionBar.newTab().setText("Tab3")
                .setTabListener(new TabsListener(Tab3Fragment)));
    }
}

public class TabsListener implements ActionBar.TabListener {

    private Fragment frag;


    // Called to create an instance of the listener when adding a new tab
    public TabsListener(Fragment networksFragment) {
        frag = networksFragment;
    }

    @Override
    public void onTabReselected(Tab arg0, FragmentTransaction arg1) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {    
        ft.add(R.id.fragment_container, frag, null);
    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        ft.remove(frag);        
    }
}

public class ListFragment1 extends ListFragment {

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {

        getListView().setItemChecked(position, true);

        ListFragment2 fragment2 = ListFragment2.newInstance(position);

        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.replace(R.id.fragment_container, fragment2);
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.addToBackStack(null);
        ft.commit();
    }
}
like image 851
andreelias Avatar asked Oct 24 '11 17:10

andreelias


1 Answers

You're not missing anything (or I'm missing it too).

I searched long and hard for a way to do this "properly" but I couldn't find anything. What I ended up doing is writing my own backstack logic. Unfortunately my employer owns my code so I can't share any of that verbatim, but here was my approach:

Create an enum with one entry for each of your tabs. Let's call it TabType. Now create an instance variable tabStacks of type HashMap<TabType, Stack<String>>. Now you can instantiate one stack for each tab - each stack is a list of tags, as specified by Fragment.getTag(). This way you don't have to worry about storing references to Fragments and whether they're going to disappear on you when you rotate the device. Any time you need a reference to a Fragment, grab the right tag off the stack and use FragmentManager.findFragmentByTag().

Now whenever you want to push a Fragment onto a tab, generate a new tag (I used UUID.randomUUID().toString()) and use it in your call to FragmentTransaction.add(). Then push the tag on top of the stack for the currently displayed tab.

Be careful: when you want to push a new fragment on top of an old one, don't remove() the old one, since the FragmentManager will consider it gone and it will be cleaned up. Be sure to detach() it, and then attach() it later. Only use remove() when you're permanently popping a Fragment, and only use add() the first time you want to show it.

Then, you'll have to add some relatively simple logic to your TabListener. When a tab is unselected, simply peek() at its stack and detatch() the associated Fragment. When a tab is selected, peek() at the top of that stack and attach() that fragment.

Lastly, you'll have to deal with Activity lifecycle quirks (like orientation changes). Persist your Map of Stacks as well as the currently selected tab, and unpack it again in your onCreate(). (You don't get this packing and unpacking for free, but it's pretty easy to do.) Luckily your TabType enum is Serializable so it should be trivial to put into a Bundle.

like image 132
jsadler Avatar answered Sep 18 '22 17:09

jsadler