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();
}
}
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With