Custom Back Stack for each Fragment in TabHost in Android
Asked Answered
M

1

6

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

Martinemartineau answered 12/10, 2012 at 7:18 Comment(15)
Check out to these easy ways to manage fragmentsRosalindrosalinda
There is no difference with the answer below.Martinemartineau
and we are adding new fragments in one of the tabFragment... how we can do thatDecrepit
@Decrepit I edited my Question. Take a look to my GitHub repository where you can find a Sample Application. GitHubMartinemartineau
@osayilgan: Did u find any solution to the issue that u have mentioned above. Would be glad if you have some solutionStadium
@TerrilThomas You can find an example on my GitHub as a solution. Take a look on this link. Example@GitHubMartinemartineau
ya i went through your github project but its not working.whenever i try to add another fragemnt into the stack its giving me null pointer FragmentTabActivity.customBackStack.get( FragmentTabActivity.mTabHost.getCurrentTabTag()).add( songList);Stadium
You need to follow the same structure. So If you want to add a new Fragment into Tabs then you need to first; 1-) create a Stack<Fragment> for each Fragment you want to add. 2-) create a constant String and put the fragment into hashmap(custom backstack) with constant string. 3-) then you need to addTab to mTabManagerMartinemartineau
I tested it and used it in one of my applications in GooglePlay. So I'm sure it works.Martinemartineau
in the onTabChanged() method , the code in if(newTab.fragment == null) and the else of this condition is the SAME. i think there is no need to add this conditionProm
@Prom Thanks, I will take a look. It's probably not needed.Martinemartineau
@osayilgan: I too am working on the problem of persisting fragment state (i.e. it should not again do a network call on tab change if previously a network request had already been performed). The problem is how do we reliably cache data (say for example, a Bitmap image) and manage memory so that an OutOfMemoryException is not thrown at some point. Would appreciate if you have some ideas on this or could share a solution you have developed. Thanks ... :)Indic
@cerebro You can use some libraries which is handling the OOM exception. like Picasso Image Loader library.Martinemartineau
@osayilgan: it is the problem of fragment re-creation that I am talking abt ... what we need to do is cache fragment data that we have downloaded, and on a tab change check whether the fragment had already downloaded its data; if so, do not perform a network call and recreate fragment using the downloaded data. This is easier said than done ... :)Indic
@cerebro If the data you mean is the bitmaps, then if you use Image Loader library in each fragment, it already caches data to the disk. So the next time you create the fragment it will get the cached data and it won't perform a network connection. As for the other data, instead of caching the Fragment (which is not wise, because fragment should be dynamic), you can store the data in an offline database. The idea of using stacks for the fragment is about stacking the fragments based on each tab. So that case you can have a different stack of fragments for each tab.Martinemartineau
E
2

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.

Escarp answered 29/10, 2012 at 7:32 Comment(4)
thanks for the answer, I'm also doing same kinda thing in my code. The issue is, if I change the tab back to the previously opened fragment, then this fragment is re-created. So I cannot save the instance state of fragment. Any idea ? is this the same with yours too ?Martinemartineau
@Martinemartineau With mine that isn't an issue, I can't point out exactly what causes this behavior in your solution as you use a different listener (onChange). But what I had problems with was adding (attaching) every fragment when switching to a different tab.Escarp
it is good as well, I will take a look to the differences between yours and mine, it can be useful. Thanks for the answer.Martinemartineau
@Martinemartineau I'm glad if it helps others.Escarp

© 2022 - 2024 — McMap. All rights reserved.