I\'ve made a simple Android Activity with an ActionBar to switch between 2 fragments. It\'s all ok until I rotate the device. In facts, when I rotate I\'ve got 2 fragment on
Thanks Martin and asclepix for theirs solutions. I have 3 tabs and first tab contains 2 fragments, like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".MainActivity" >
<FrameLayout
android:id="@+id/frActiveTask"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
/>
<FrameLayout
android:id="@+id/frTaskList"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_above="@id/frActiveTask"
/>
</RelativeLayout>
Using onRestoreInstanceState
, onSaveInstanceState
and savedInstanceState.remove("android:support:fragments");
methods and statement working almost fine except if your active tab is not the first one and rotate and click on first, a clear display appears and only for the second click on the first tab came the right fragment display.
After debugging code I recognized that the first addTab
always calls an onTabSelected
event in the tab listener, with a fragment add
method and then when the setSelectedNavigationItem
is called from onRestoreInstanceState
a detach
is executed on the first tab and an add
for other one.
This unecessary add
calling is fixed in my solution.
My activity
protected void onCreate(Bundle savedInstanceState) {
boolean firstTabIsNotAdded = false;
if (savedInstanceState != null) {
savedInstanceState.remove("android:support:fragments");
firstTabIsNotAdded = savedInstanceState.getInt(SELETED_TAB_INDEX) != 0;
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// codes before adding tabs
actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
tabStartAndStop = actionBar.newTab().setText(getString(R.string.tab_title_start_and_stop))
.setTabListener(
new FragmentTabListener<StartStopFragment>(this,
getString(R.string.tab_title_start_and_stop_id),
StartStopFragment.class,
firstTabIsNotAdded));
tabHistory = actionBar.newTab().setText(getString(R.string.tab_title_history))
.setTabListener(
new FragmentTabListener<HistoryFragment>(this,
getString(R.string.tab_title_history_id),
HistoryFragment.class,
false));
tabRiporting = actionBar.newTab().setText(getString(R.string.tab_title_reporting))
.setTabListener(
new FragmentTabListener<ReportingFragment>(this,
getString(R.string.tab_title_reporting_id),
ReportingFragment.class,
false));
actionBar.addTab(tabStartAndStop);
actionBar.addTab(tabHistory);
actionBar.addTab(tabRiporting);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState != null) {
int index = savedInstanceState.getInt(SELETED_TAB_INDEX);
actionBar.setSelectedNavigationItem(index);
}
super.onRestoreInstanceState(savedInstanceState);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save the index of the currently selected tab
outState.putInt(SELETED_TAB_INDEX, getSupportActionBar().getSelectedTab().getPosition());
}
And the modified tab listener
public class FragmentTabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener {
private Fragment mFragment;
private final Activity mFragmentActivity;
private final String mTag;
private final Class<T> mClass;
private boolean doNotAdd;
/** Constructor used each time a new tab is created.
* @param activity The host Activity, used to instantiate the fragment
* @param tag The identifier tag for the fragment
* @param clz The fragment's Class, used to instantiate the fragment
*/
public FragmentTabListener(Activity activity, String tag, Class<T> clz, boolean doNotAdd) {
mFragmentActivity = activity;
mTag = tag;
mClass = clz;
this.doNotAdd = doNotAdd;
}
/* The following are each of the ActionBar.TabListener callbacks */
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
if(doNotAdd){
doNotAdd = false;
}else{
mFragment = Fragment.instantiate(mFragmentActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
}
} else {
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
// Detach the fragment, because another one is being attached
ft.detach(mFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// User selected the already selected tab. Usually do nothing.
}
}
The solution does not actually require a whole lot of work, the following steps ensure, that when rotating the screen, the tab selection is maintained. I ran into overlapping Fragments, because upon screen rotation my first tab was selected, not the second one that was selected before rotating the screen and hence the first tab was overlapping the content of the second tab.
This is how your Activity should look (I am using ActionBarSherlock but adjusting it should be very easy):
public class TabHostActivity extends SherlockFragmentActivity {
private static final String SELETED_TAB_INDEX = "tabIndex";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Setup the action bar
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Create the Tabs you need and add them to the actionBar...
if (savedInstanceState != null) {
// Select the tab that was selected before orientation change
int index = savedInstanceState.getInt(SELETED_TAB_INDEX);
actionBar.setSelectedNavigationItem(index);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save the index of the currently selected tab
outState.putInt(SELETED_TAB_INDEX, getSupportActionBar().getSelectedTab().getPosition());
}
}
And this is what my ActionBar.TabListener looks like (its a private class in the above Activity):
private class MyTabsListener<T extends Fragment> implements ActionBar.TabListener {
private Fragment fragment;
private final SherlockFragmentActivity host;
private final Class<Fragment> type;
private String tag;
public MyTabsListener(SherlockFragmentActivity parent, String tag, Class type) {
this.host = parent;
this.tag = tag;
this.type = type;
}
@Override
public void onTabSelected(Tab tab, FragmentTransaction transaction) {
/*
* The fragment which has been added to this listener may have been
* replaced (can be the case for lists when drilling down), but if the
* tag has been retained, we should find the actual fragment that was
* showing in this tab before the user switched to another.
*/
Fragment currentlyShowing = host.getSupportFragmentManager().findFragmentByTag(tag);
// Check if the fragment is already initialised
if (currentlyShowing == null) {
// If not, instantiate and add it to the activity
fragment = SherlockFragment.instantiate(host, type.getName());
transaction.add(android.R.id.content, fragment, tag);
} else {
// If it exists, simply attach it in order to show it
transaction.attach(currentlyShowing);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction fragmentTransaction) {
/*
* The fragment which has been added to this listener may have been
* replaced (can be the case for lists when drilling down), but if the
* tag has been retained, we should find the actual fragment that's
* currently active.
*/
Fragment currentlyShowing = host.getSupportFragmentManager().findFragmentByTag(tag);
if (currentlyShowing != null) {
// Detach the fragment, another tab has been selected
fragmentTransaction.detach(currentlyShowing);
} else if (this.fragment != null) {
fragmentTransaction.detach(fragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction fragmentTransaction) {
// This tab is already selected
}
The above implementation also allows replacement of Fragments within a tab, based on their tags. For this purpose when switching fragments within the same tab I use the same Tag name, that was used for the initial framework that's been added to the tab.
I've resolved using onSaveInstanceState
and onRestoreInstanceState
in the Activity to maintain the selected tab and modifying onTabSelected
as follows.
The last modify avoids that Android rebuild the Fragment it doesn't destoy. However I don't understand why the Activity is destroyed by the rotation event while the current Fragment no. (Have you some idea about this?)
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// previous Fragment management
Fragment prevFragment;
FragmentManager fm = mActivity.getFragmentManager();
prevFragment = fm.findFragmentByTag(mTag);
if (prevFragment != null) {
mFragment = prevFragment;
} // \previous Fragment management
// 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());
ft.add(android.R.id.content, mFragment, mTag);
} else {
// If it exists, simply attach it in order to show it
ft.attach(mFragment);
}
}
Since I use a android.support.v4.view.ViewPager
overriding onTabSelected
would not help. But still you hint pointed me in the right direction.
The android.support.v4.app.FragmentManager
saves all fragments in the onSaveInstanceState
of the android.support.v4.app.FragmentActivity
. Ignoring setRetainInstance
— Depending on your design this might lead to duplicate fragments.
The simplest solution is to delete the saved fragment in the orCreate
of the activity:
@Override
public void onCreate (final android.os.Bundle savedInstanceState)
{
if (savedInstanceState != null)
{
savedInstanceState.remove ("android:support:fragments");
} // if
super.onCreate (savedInstanceState);
…
return;
} // onCreate