31

Current, I have 2 Fragments, which is switch-able through ActionBar's tab.

    getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    ActionBar.Tab newTab = getSupportActionBar().newTab();
    newTab.setText("history");
    newTab.setTabListener(new TabListenerHistoryFragment>(this, "history",
        HistoryFragment.class));

 @Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    // Check if the fragment is already initialized
    if (mFragment == null) {

        // If not, instantiate and add it to the activity
        mFragment = Fragment.instantiate(mActivity, mClass.getName());
        mFragment.setRetainInstance(true);
        ft.add(android.R.id.content, mFragment, mTag);
    } else {
        // If it exists, simply attach it in order to show it
        ft.attach(mFragment);
    }        
}

I realize the first time of my Activity (This activity is holding 2 fragments) being launched, Fragments' methods will be called in the following sequence.

onCreate -> onCreateView -> onStart

When I perform Tab switching, and then Tab switching back to the same Fragment, the following methods will be called again.

onCreateView -> onStart

I just wish to retain the same GUI view state, when Tab is being switched back.

  • I want my chart continue to be zoomed into previous level.
  • I want my chart horizontal scroll stay at previous level.
  • I want my list continue scroll stay at previous level.
  • ...

I know that I can save/restore simple variables using the following method when Tab switching

android fragment- How to save states of views in a fragment when another fragment is pushed on top of it

But, that is not something I want, as my GUI state is pretty difficult to describe within whole bunch of primitive values.

I try the following approach. Of course it won't work, as I am getting the following runtime error.

public class HistoryFragment extends Fragment {
    View view = null;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (this.view != null) {
            return this.view;
        }
        this.view = inflater.inflate(R.layout.history_activity, container, false); 
    }
}

java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

I realize the following demo example is able to preserve its fragment GUI state (For instance, the position of vertical scroll of list) when there is Tab switching. But I guess, perhaps it is because they are using ListFragment? As I do not find they perform any special handling to preserve GUI state.

  • com.example.android.apis.app.FragmentTabs
  • com.example.android.apis.app.LoaderCursor.CursorLoaderListFragment

May I know, how I can avoid from recreating same view when perform tab switching?

Community
  • 1
  • 1
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

6 Answers6

36

I had the same problem, and tried to follow the suggestion in the error message. I tried the following code, and it worked for me.

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state)  {
     if (mMyView == null) {
         mMyView = new MyView(getActivity());
     } else {
         ((ViewGroup) mMyView.getParent()).removeView(mMyView);
     }

     return mPuzzleView; 
}
biegleux
  • 13,179
  • 11
  • 45
  • 52
roger
  • 376
  • 4
  • 3
19

I started searching for a simple solution for this many hours ago and finally stumbled across the answer by @roger which saved me lots of hair....

When using the ViewPager in other implementations, I could simply call:

mViewPager.setOffscreenPageLimit(//number of pages to cache);

So, I was very surprised it took me so many hours to resolve this. The example he gave wasn't entirely clear though, so for the sake of completeness, here is the code I use for the Fragments in my FragmentTabHost

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class FragmentExample extends Fragment {

    private View rootView;

    public FragmentExample() {
    }

    @Override
    public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {

        if (rootView == null) {

            rootView = inflater.inflate(R.layout.fragment_example_layout, container, false);

            // Initialise your layout here

        } else {
            ((ViewGroup) rootView.getParent()).removeView(rootView);
        }

        return rootView;
    }
}

I searched for the following key phrases which I'm adding here, in the hope that I may save someone else from the frustration I've just experienced!


FragmentTabHost save Fragment state

FragmentTabHost views recreated

FragmentTabHost cache Fragments

FragmentTabHost onCreateView Fragment destroyed


Community
  • 1
  • 1
brandall
  • 6,094
  • 4
  • 49
  • 103
6

The following solution works for me. It prevents Fragment's onCreateView to be called when switching tabs.

Activity's onCreate should add all fragments and hide all except the one for the first tab:

ft.add(R.id.fragment_content, secondTabFragment);
ft.hide(secondTabFragment);
ft.add(R.id.fragment_content, firstTabFragment);
ft.show(firstTabFragment);
ft.commit();
currentFragment = firstTabFragment;

Activity's onTabSelected should just hide the current fragment and show the fragment corresponding to the chosen tab.

ft.hide(currentFragment);
ft.show(chosenFragment);
ft.commit();
currentFragment = chosenFragment;

Beware that changing the device orientation will restart your Activity and then recreate your Fragments. You can avoid that by adding this configChanges in your Manifest:

<activity android:configChanges="keyboardHidden|orientation" ...
Olivier C
  • 1,151
  • 10
  • 11
  • but @Olivier C... I'm unable to use getSupportFragmentManager() method because I'm implementing the ActionBar SHerlock.... please what should I do then? – gumuruh Sep 12 '14 at 09:17
3
View mMyView = null;     
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state)  {
         if (state == null) {
             mMyView = new MyView(getActivity());
         } else {
             container.removeView(mMyView);
         }

         return mMyView; 
    }
Steven Mark Ford
  • 3,372
  • 21
  • 32
0

Update

I simply avoiding this problem, by using ViewPager instead of ActionBar's tab.

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
-1

I faced the same issue, but what I did was, before attaching or detaching the fragement inside the callbacks of ActionBar.TabListener, call

fragmentManager.executePendingTransactions();

this solves the issue for me

@Override
public void onTabelected(Tab tab, FragmentTransaction ft, FragmentManager fm) {
    fm.executePendingTransactions(); // **execute the pending transactions before adding another fragment.
    if (mFragment == null) {
        mFragment = Fragment.instantiate(mContext, mFragmentName);
        ft.replace(android.R.id.tabcontent, mFragment, mTag);
    } else {
        ft.attach(mFragment);
    }
}
Vishnu Prabhu
  • 449
  • 1
  • 5
  • 19