morristech
2/2/2018 - 10:58 AM

Complete working solution for Android action bar tabs with fragments having separate back stack for each tab.

Complete working solution for Android action bar tabs with fragments having separate back stack for each tab.

// Custom interface that enables communication between Fragment and its Activity
public interface OnItemSelectedListener
{
	public void onItemSelected(String item);
}
/*
 * This code would not compile as-is. It will require custom Fragment classes, fragment interaction
 * interfaces, animation resources. But it's fully functional in terms of correctly manipulating
 * individual back stacks for each tab, preserving state not only on tab switch but also on device
 * rotation and application switch. To work properly your Fragments should use setRetainInstance(true)
 * or implement their individual state saving via onSaveInstanceState() and state restoring.
 *
 * This code is intentionally as simple as it can be, you can extend it to support custom animations,
 * variable number of tabs, action bar 'up' navigation, etc.
 */
 
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
import java.util.UUID;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.app.SherlockFragmentActivity;

// OnItemSelectedListener is a custom interface that enables communication between Fragment and
// its Activity (see bottom of the code for more information)
public class TabActivity extends SherlockFragmentActivity implements ActionBar.TabListener, OnItemSelectedListener
{
	enum TabType
	{
		SEARCH, LIST, FAVORITES
	}

	// Tab back stacks
	private HashMap<TabType, Stack<String>> backStacks;

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

		// Initialize ActionBar
		ActionBar bar = getSupportActionBar();
		bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

		// Set back stacks
		if (savedInstanceState != null)
		{
			// Read back stacks after orientation change
			backStacks = (HashMap<TabType, Stack<String>>) savedInstanceState.getSerializable("stacks");
		}
		else
		{
			// Initialize back stacks on first run
			backStacks = new HashMap<TabType, Stack<String>>();
			backStacks.put(TabType.SEARCH, new Stack<String>());
			backStacks.put(TabType.LIST, new Stack<String>());
			backStacks.put(TabType.FAVORITES, new Stack<String>());
		}
		
		// Create tabs
		bar.addTab(bar.newTab().setTag(TabType.SEARCH).setText("Search").setTabListener(this));
		bar.addTab(bar.newTab().setTag(TabType.LIST).setText("List").setTabListener(this));
		bar.addTab(bar.newTab().setTag(TabType.FAVORITES).setText("Favorites").setTabListener(this));
	}

	@Override
	protected void onResume()
	{
		super.onResume();
		// Select proper stack
		Tab tab = getSupportActionBar().getSelectedTab();
		Stack<String> backStack = backStacks.get(tab.getTag());
		if (! backStack.isEmpty())
		{
			// Restore topmost fragment (e.g. after application switch)
			String tag = backStack.peek();
			Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
			if (fragment.isDetached())
			{
				FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
				ft.attach(fragment);
				ft.commit();
			}
		}
	}

	@Override
	protected void onPause()
	{
		super.onPause();
		// Select proper stack
		Tab tab = getSupportActionBar().getSelectedTab();
		Stack<String> backStack = backStacks.get(tab.getTag());
		if (! backStack.isEmpty())
		{
			// Detach topmost fragment otherwise it will not be correctly displayed
			// after orientation change
			String tag = backStack.peek();
			FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
			Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
			ft.detach(fragment);
			ft.commit();
		}
	}

	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState)
	{
		super.onRestoreInstanceState(savedInstanceState);
		// Restore selected tab
		int saved = savedInstanceState.getInt("tab", 0);
		if (saved != getSupportActionBar().getSelectedNavigationIndex())
			getSupportActionBar().setSelectedNavigationItem(saved);
	}

	@Override
	protected void onSaveInstanceState(Bundle outState)
	{
		super.onSaveInstanceState(outState);
		// Save selected tab and all back stacks
		outState.putInt("tab", getSupportActionBar().getSelectedNavigationIndex());
		outState.putSerializable("stacks", backStacks);
	}

	@Override
	public void onBackPressed()
	{
		// Select proper stack
		Tab tab = getSupportActionBar().getSelectedTab();
		Stack<String> backStack = backStacks.get(tab.getTag());
		String tag = backStack.pop();
		if (backStack.isEmpty())
		{
			// Let application finish
			super.onBackPressed();
		}
		else
		{
			FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
			Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
			// Animate return to previous fragment
			ft.setCustomAnimations(R.anim.slide_from_right, R.anim.slide_to_left);
			// Remove topmost fragment from back stack and forget it
			ft.remove(fragment);
			showFragment(backStack, ft);
			ft.commit();
		}
	}

	@Override
	public void onTabSelected(Tab tab, FragmentTransaction ft)
	{
		// Select proper stack
		Stack<String> backStack = backStacks.get(tab.getTag());
		if (backStack.isEmpty())
		{
			// If it is empty instantiate and add initial tab fragment
			Fragment fragment;
			switch ((TabType) tab.getTag())
			{
				case SEARCH:
					fragment = Fragment.instantiate(this, SearchFragment.class.getName());
					break;
				case LIST:
					fragment = Fragment.instantiate(this, ListFragment.class.getName());
					break;
				case FAVORITES:
					fragment = Fragment.instantiate(this, FavoritesFragment.class.getName());
					break;
				default:
					throw new java.lang.IllegalArgumentException("Unknown tab");
			}
			addFragment(fragment, backStack, ft);
		}
		else
		{
			// Show topmost fragment
			showFragment(backStack, ft);
		}
	}

	@Override
	public void onTabUnselected(Tab tab, FragmentTransaction ft)
	{
		// Select proper stack
		Stack<String> backStack = backStacks.get(tab.getTag());
		// Get topmost fragment
		String tag = backStack.peek();
		Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
		// Detach it
		ft.detach(fragment);
	}

	@Override
	public void onTabReselected(Tab tab, FragmentTransaction ft)
	{
		// Select proper stack
		Stack<String> backStack = backStacks.get(tab.getTag());

		if (backStack.size() > 1)
			ft.setCustomAnimations(R.anim.slide_from_right, R.anim.slide_to_left);
		// Clean the stack leaving only initial fragment
		while (backStack.size() > 1)
		{
			// Pop topmost fragment
			String tag = backStack.pop();
			Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
			// Remove it
			ft.remove(fragment);
		}
		showFragment(backStack, ft);
	}

	private void addFragment(Fragment fragment)
	{
		// Select proper stack
		Tab tab = getSupportActionBar().getSelectedTab();
		Stack<String> backStack = backStacks.get(tab.getTag());

		FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
		// Animate transfer to new fragment
		ft.setCustomAnimations(R.anim.slide_from_left, R.anim.slide_to_right);
		// Get topmost fragment
		String tag = backStack.peek();
		Fragment top = getSupportFragmentManager().findFragmentByTag(tag);
		ft.detach(top);
		// Add new fragment
		addFragment(fragment, backStack, ft);
		ft.commit();
	}

	private void addFragment(Fragment fragment, Stack<String> backStack, FragmentTransaction ft)
	{
		// Add fragment to back stack with unique tag
		String tag = UUID.randomUUID().toString();
		ft.add(android.R.id.content, fragment, tag);
		backStack.push(tag);
	}

	private void showFragment(Stack<String> backStack, FragmentTransaction ft)
	{
		// Peek topmost fragment from the stack
		String tag = backStack.peek();
		Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
		// and attach it
		ft.attach(fragment);		
	}

	// The following code shows how to properly open new fragment. It assumes
	// that parent fragment calls its activity via interface. This approach
	// is described in Android development guidelines.
	@Override
	public void onItemSelected(String item)
	{
		ItemFragment fragment = new ItemFragment();
		Bundle args = new Bundle();
		args.putString("item", item);
		fragment.setArguments(args);
		addFragment(fragment);
	}
}