Last active
June 14, 2020 17:43
-
-
Save andreynovikov/4619215 to your computer and use it in GitHub Desktop.
Complete working solution for Android action bar tabs with fragments having separate back stack for each tab.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Custom interface that enables communication between Fragment and its Activity | |
public interface OnItemSelectedListener | |
{ | |
public void onItemSelected(String item); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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); | |
} | |
} |
As for the comment above, this defeats the entire purpose of the gist. OP is trying to manage separate 'backstacks' for each tab in his activity.
As for this gist in general, while OP is detaching (deflating UI components) past fragments he is not removing these fragment states from memory or reversing the general case of the transaction that added it. So it fails to fully stand in for the function of the backstack which is to parcel the fragment transaction and relevant fragment state to disk and reverse the transaction on reattach.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
We can use this method: http://developer.android.com/training/basics/fragments/fragment-ui.html#Replace
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
FragmentTransaction.addToBackStage() method saves the transaction, and reverses the transaction when back button is pressed.
Using this method, when user presses back button, it shows previous fragment of current tab, & when fragment stack gets empty of current tab, it exits the app.