Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Back Stack for each Fragment in TabHost in Android

Since the TabActivity is deprecated I tried to replace it with Fragments which has been already mentioned in developer android website. But as you guys already know there was an issue about replacing tabs with fragments, since there will be only one activity ,which is Fragment Activity, there is no back stack for each of the fragments and as you can see in the other SO questions most of the developers were saying that you need to manage your own custom back stack as a solution.

I have created and implemented my own custom back stack as you can see below, it works perfect I can track each fragment in each tab.

Tab1

    Fragment1 > Fragment2 > Fragment3

Tab2

    Fragment5 > Fragment6

According to navigation above, if I navigate from Fragment1 through Fragment 3 and After that If I change the tab to Tab2 to and come back to Tab1, I can still see the Fragment3 and I can even navigate back to Fragment1 with my custom back stack.

But the issue is, When I come back to Tab1 and see the Fragment3, the Fragment3 is re-created and I can not see the changes as I left behind before I change the tab to Tab2.

Here is my Custom back stack;

public static HashMap<String, Stack<Fragment>> customBackStack;

public static Stack<Fragment> simpleStack;
public static Stack<Fragment> contactStack;
public static Stack<Fragment> customStack;
public static Stack<Fragment> throttleStack;
public static Stack<Fragment> homeStack;

customBackStack = new HashMap<String, Stack<Fragment>>();

homeStack = new Stack<Fragment>();
simpleStack = new Stack<Fragment>();
contactStack = new Stack<Fragment>();
customStack = new Stack<Fragment>();
throttleStack = new Stack<Fragment>();

customBackStack.put("home", homeStack);
customBackStack.put("simple", simpleStack);
customBackStack.put("contacts", contactStack);
customBackStack.put("custom", customStack);
customBackStack.put("throttle", throttleStack);

And here is onTabChanged method;

        public void onTabChanged(String tabId) {
        TabInfo newTab = mTabs.get(tabId);
        if (mLastTab != newTab) {
            FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
            if (mLastTab != null) {
                if (mLastTab.fragment != null) {
                    ft.detach(mLastTab.fragment);
                }
            }
            if (newTab != null) {
                if (newTab.fragment == null) {
                    if (!customBackStack.get(tabId).isEmpty()) {
                        Fragment fragment = customBackStack.get(tabId).pop();
                        customBackStack.get(tabId).push(fragment);
                        ft.replace(mContainerId, fragment);
                    }
                } else {
                    if (!customBackStack.get(tabId).isEmpty()) {
                        Fragment fragment = customBackStack.get(tabId).pop();
                        customBackStack.get(tabId).push(fragment);
                        ft.replace(mContainerId, fragment);
                    }
                }
            }

            mLastTab = newTab;
            ft.commit();
            mActivity.getSupportFragmentManager().executePendingTransactions();
        }
    }

And here is onBackPressed;

    public void onBackPressed() {

    Stack<Fragment> stack = customBackStack.get(mTabHost.getCurrentTabTag());

    if (stack.isEmpty()) {
        super.onBackPressed();
    } else {

        Fragment fragment = stack.pop();

        if (fragment.isVisible()) {
            if (stack.isEmpty()) {
                super.onBackPressed();
            } else {
                Fragment frg = stack.pop();
                customBackStack.get(mTabHost.getCurrentTabTag()).push(frg);

                transaction = getSupportFragmentManager().beginTransaction();
                transaction.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right,
                        R.anim.slide_in_right, R.anim.slide_out_left);

                transaction.replace(R.id.realtabcontent, frg).commit();
            }
        } else {
            getSupportFragmentManager().beginTransaction().replace(R.id.realtabcontent, fragment).commit();
        }
    }

}

As a result, custom back stack works fine except the last fragment before changing the tab is getting re-created after coming back to the tab, its not resuming as like in tab activity.

Any idea how to solve it ?

EDIT

You can find my sample Application here as a solution regarding to this issue.

GitHub

like image 243
osayilgan Avatar asked Oct 12 '12 07:10

osayilgan


1 Answers

I modified googles sample tablistener to following:

public static class TabListener<T extends SherlockFragment> implements ActionBar.TabListener {
    private final SherlockFragmentActivity mActivity;
    private final String mTag;
    private final Class<T> mClass;
    private final Bundle mArgs;
    public SherlockFragment mFragment;
    private final Stack<SherlockFragment> mStack;

    public TabListener(SherlockFragmentActivity activity, String tag, Class<T> clz) {
        this(activity, tag, clz, null, null);
    }

    public TabListener(SherlockFragmentActivity activity, String tag, Class<T> clz, Bundle args, Stack<SherlockFragment> stack) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
        mArgs = args;
        mStack = stack;

        // Check to see if we already have a fragment for this tab, probably
        // from a previously saved state.  If so, deactivate it, because our
        // initial state is that a tab isn't shown.
        mFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);
        if (mFragment != null && !mFragment.isDetached()) {
            FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
            ft.detach(mFragment);
            ft.commit();
        }
    }

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        // we need to reattach ALL fragments thats in the custom stack, if we don't we'll have problems with setCustomAnimation for fragments.
        if (mFragment == null) {
            mFragment = (SherlockFragment) SherlockFragment.instantiate(mActivity, mClass.getName(), mArgs);
            ft.replace(android.R.id.content, mFragment, mTag);
        } else {
            ft.attach(mFragment);
        }
        if(mStack != null) {
            for(SherlockFragment fragment: mStack) {
                ft.attach(fragment);
            }
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        if (mFragment != null) {
            ft.detach(mFragment);
        }
        if(mStack != null) {
            for(SherlockFragment fragment: mStack) {
                if(fragment!= null && !fragment.isDetached()) {
                    ft.detach(fragment);
                }
            }
        }
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
}

And the activity onKeyDown methode I override to following:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        int index = getSupportActionBar().getSelectedNavigationIndex();
        Stack<SherlockFragment> stack = null;
        SherlockFragment fragment = null;
        String rootTag = null;
        switch(index) {
            case 0:
                stack = mWatersTabListener.mStack;
                fragment = mWatersTabListener.mFragment;
                rootTag = mWatersTabListener.mTag;
                break;
            case 1:
                stack = mHarborTabListener.mStack;
                fragment = mHarborTabListener.mFragment;
                rootTag = mHarborTabListener.mTag;
                break;
        }

        if(stack.isEmpty()) {
            return super.onKeyDown(keyCode, event);
        } else {
            SherlockFragment topFragment = stack.pop();
            FragmentManager fragmentManager = getSupportFragmentManager();

            FragmentTransaction ft = fragmentManager.beginTransaction();

            ft.setCustomAnimations(R.anim.fragment_slide_right_enter,
                    R.anim.fragment_slide_right_exit);

            if(topFragment != null && !topFragment.isDetached()) {
                ft.detach(topFragment);
            }

            if(stack.isEmpty()) {
                ft.replace(android.R.id.content, fragment, rootTag);
                ft.commit();
                return true;
            } else {
                SherlockFragment nextFragment = stack.peek();
                ft.replace(android.R.id.content, nextFragment);
                ft.commit();
                return true;
            }
        }
    }
    return super.onKeyDown(keyCode, event);
}

The important things to take note of is that you have to attach all fragments in your custom stack when selected and you have to detach all as well when unselected. If there is problem with understanding the code snippets just ask.

like image 177
Warpzit Avatar answered Sep 30 '22 12:09

Warpzit