Info: I have a 2 pane layout (2 child Fragments) inside a ParentFragment, which, of course, is inside a FragmentActivity. I have setRetainInstance(true) on the ParentFragment. On orientation change, the left child fragment doesn't get destroyed (onCreate() doesn't get called), which is normal (because of the parent retaining its instance).
Problem: On orientation change, the right fragment gets destroyed (onCreate() gets called). Why the hell is the right fragment destroyed and the left one isn't ?
EDIT: If I remove setRetainInstance(true), then the left fragment's onCreate() gets called twice (lol wtf) and the right fragment's onCreate() gets called once. So this isn't good either...
Code below for the ParentFragment:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_schedule, container, false);
setHasOptionsMenu(true);
if (getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left) == null ||
!getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left).isInLayout())
{
if (mPresentationsListFragment == null)
mPresentationsListFragment = PresentationsListFragment.newInstance(PresentationsListFragment.TYPE_SCHEDULE, mScheduleDate);
getChildFragmentManager().beginTransaction()
.replace(R.id.fragment_schedule_framelayout_left, mPresentationsListFragment)
.commit();
}
mPresentationsListFragment.setOnPresentationClickListener(this);
return view;
}
#Override
public void onPresentationClick(int id)
{
if (Application.isDeviceTablet(getActivity()))
{
if (getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_right) == null)
{
if (mPresentationDetailFragment == null)
mPresentationDetailFragment = PresentationDetailFragment.newInstance(id);
else
mPresentationDetailFragment.loadPresentation(id);
getChildFragmentManager().beginTransaction()
.replace(R.id.fragment_schedule_framelayout_right, mPresentationDetailFragment)
.commit();
}
else
mPresentationDetailFragment.loadPresentation(id);
}
else
{
Intent presentationDetailIntent = new Intent(getActivity(), PresentationDetailActivity.class);
presentationDetailIntent.putExtra(PresentationDetailActivity.KEY_PRESENTATION_ID, id);
startActivity(presentationDetailIntent);
}
}
LE Solution:
Thanks a lot to antonyt , the answer is below. The only changes needed to pe performed reside inside onCreateView() of the parent Fragment.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_schedule, container, false);
setHasOptionsMenu(true);
if (getChildFragmentManager().findFragmentById(R.id.fragment_presentations_framelayout_left) == null)
{
mPresentationsListFragment = PresentationsListFragment.newInstance();
mPresentationsListFragment.setOnPresentationClickListener(this);
getChildFragmentManager().beginTransaction()
.add(R.id.fragment_presentations_framelayout_left, mPresentationsListFragment)
.commit();
}
return view;
}
From what I understand, if you have setRetainInstance(true) on the parent fragment with the above code, your left fragment should be recreated but your right fragment should not be, when changing orientation. This is backwards to what you wrote above, but I will explain why this is the case anyway. If you have setRetainInstance(false) on the parent fragment, you indeed should see the left fragment being created twice and the right fragment being created once.
Case 1: setRetainInstance(true)
Your parent fragment will not be destroyed on rotation. However, it will still recreate its views each time (onDestroyView and onCreateView will be called, in that order). In onCreateView you have code to add your left fragment under certain conditions. getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left) should be non-null, since a fragment was added to that container previously. getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left).isInLayout() should be false since only fragments added via XML will cause it to return true. The overall condition is true and so a new instance of your left fragment will be created and it will replace the old one. Your right fragment is only instantiated during a click event and so no special behavior happens.
Summary: Parent fragment remains, new left fragment is created, right fragment remains.
Case 2: setRetainInstance(false)
Your parent fragment is destroyed, and so are the left and right fragments. All three fragments are recreated automatically by Android. Your parent fragment will then get a chance to create its view, and it will create a new instance of the left fragment as per the explanation above. The just-created left fragment will be replaced by this new instance. You will observe that a left fragment will be destroyed and another left fragment will be created. No special behavior happens for the right fragment.
Summary: New parent fragment is created, two new left fragments are created, new right fragment is created.
If you are sure that in the setRetainInstance(true) case, your right fragment is being destroyed and not your left one, please post a sample project to github/etc. that demonstrates this.
Update: Why the right fragment gets removed if you use FragmentTransaction.replace() on the left fragment
Because of the inner conditional, your code will try to replace your left fragment with itself on the same container.
Here is the code snippet from the Android 4.1 source code that handles a replace:
...
case OP_REPLACE: {
Fragment f = op.fragment;
if (mManager.mAdded != null) {
for (int i=0; i<mManager.mAdded.size(); i++) {
Fragment old = mManager.mAdded.get(i);
if (FragmentManagerImpl.DEBUG) Log.v(TAG,
"OP_REPLACE: adding=" + f + " old=" + old);
if (f == null || old.mContainerId == f.mContainerId) {
if (old == f) {
op.fragment = f = null;
} else {
if (op.removed == null) {
op.removed = new ArrayList<Fragment>();
}
op.removed.add(old);
old.mNextAnim = op.exitAnim;
if (mAddToBackStack) {
old.mBackStackNesting += 1;
if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
+ old + " to " + old.mBackStackNesting);
}
mManager.removeFragment(old, mTransition, mTransitionStyle);
}
}
}
}
if (f != null) {
f.mNextAnim = op.enterAnim;
mManager.addFragment(f, false);
}
} break;
...
If you try to replace the same fragment with itself, there is some code to try and ignore this operation:
if (old == f) {
op.fragment = f = null;
}
Since f is null, and we are still continuing to iterate through our fragments, this seems to have the side effect of removing every subsequent fragment from the FragmentManager. I don't think this is intentional, but at the very least explains why your right fragment is getting destroyed. Not using replace / not replacing the same fragment with itself can fix your issues.
Interestingly, this was a recent change and did not exist in previous versions of Android.
https://github.com/android/platform_frameworks_support/commit/5506618c80a292ac275d8b0c1046b446c7f58836
Bug report: https://code.google.com/p/android/issues/detail?id=43265
Related
The main page of my application has a FrameLayout.
I'm instantiating two fragments when the activity starts, and I'm trying to use a menu button to swap between the fragment.
scanHistoryFrag = new HistoryFragment();
scanFrag = new ScanFragment();
I never replace these objects - I use the same ones throughout the lifecycle of the application. However, when I swap them in my FrameLayout...
private void ChangeFragment(Android.Support.V4.App.Fragment fragment)
{
Android.Support.V4.App.FragmentTransaction ft = SupportFragmentManager.BeginTransaction();
ft.Replace(Resource.Id.fragmentContainer, fragment);
ft.Commit();
}
OnCreate and OnCreateView are called on the Fragment again... which means any adjustments I made post creation on that fragment are overwritten with initial values again. I can't seem to find any explanation for why this is happening or how I might avoid it.
The ChangeFragment method is being called by OnOptionsItemSelected, as I'm using a menu button to toggle them.
I never replace these objects - I use the same ones throughout the lifecycle of the application.
Initialization of a subclass of Fragment just create a instance of this class object, the constructor of this class will be called, but it will not go through the lifecycle of Fragment unless this Fragment is added, for more information, you can refer to Fragments. To understand it easier, I personal think the instance saves the data state of this Fragment class, but the events of lifecycle handle the view state of this Fragment.
which means any adjustments I made post creation on that fragment are overwritten with initial values again.
Yes, you're right. To avoid overwritting with initial values again, we can cache the fragment's view in OnCreateView for example like this:
private View rootView;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Use this to return your custom view for this Fragment
// return inflater.Inflate(Resource.Layout.YourFragment, container, false);
if (rootView == null)
{
//first time creating this fragment view
rootView = inflater.Inflate(Resource.Layout.fragmentlayout1, container, false);
//Initialization
//TODO:
}
else
{
//not first time creating this fragment view
ViewGroup parent = (ViewGroup)rootView.Parent;
if (parent != null)
{
parent.RemoveView(rootView);
}
}
return rootView;
}
I have a few fragments that are loaded when a user clicks on an item in a list. Say a user has clicked on second item in the list, loading the second fragment. But, upon rotating, the screen, the first fragment in the list gets loaded. How can I make sure that the same fragment gets loaded whenever a user rotates the screen.
This is how I'm loading my fragments
private void selectItem(position) {
Fragment fragment = null;
switch (position) {
case 0:
fragment = new FirstFragment();
break;
case 1:
fragment = new SecondFragment();
break;
case 2:
fragment = new ThirdFragment();
break;
default:
break;
}
if (fragment != null) {
android.app.FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
}
else {
Log.e("NavigationActivity", "Error in creating fragment");
}
}
I'm calling selectItem(0) in onCreate of an activity.
The entire activity gets destroyed and recreated during a rotation. So if you are calling setItem(0) in Activity.onCreate, then you'll always get FirstFragment in the content frame.
Seems like the easy thing may be to just detect if you've already set a fragment in onCreate and not load the default. Either make use of onSaveInstanceState and/or mark the fragment as retained.
I don't have much experience with retained fragments or fragment management beyond initial load, so just using onSaveInstanceState to keep track of which one was loaded seems appropriate.
In your Activity, override onSaveInstanceState:
#Override
public void onSaveInstanceState(Bundle bundle)
{
bundle.putInt("which_fragment", _fragmentId);
super.onSaveInstanceState(bundle);
}
Where _fragmentId is just some numerical identifier of the particular fragment you are loading. It could even be it's layout id. Set this value in your selectItem method.
And then in onCreate:
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
_fragmentId = 0;
if (savedInstanceState != null)
{
_fragmentId = savedInstanceState.getInt("which_fragment", 0);
}
...
selectItem(_fragmentId);
}
First of all I wouldn't use positionOnTheList->Fragment dependency. I would depend on some id (final or from the resources).
Secondly I think you shouldn't create a new instance of each Fragment class when you select item from the list.
You should consider this approach:
Fragment f = fragmentManager.findFragmentById( String.valueOf(id) );
if( f == null )
f = new FragmentDependingOnId();
mCurrentlySelectedId = id;
fragmentManager.beginTransaction()
.replace( R.id.container, f , String.valueOf(id))
.commit();
Add the following method:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SELECTED_ID, mCurrentlySelectedId);
}
and in onCreate add:
if(savedInstanceState!=null){
mCurrentlySelectedId = savedInstanceState.getInt(SELECTED_ID);
selectItem(mCurrentlySelectedId);
}
When using fragment you usually use onCreateView to inflate your layout. Then you use onActivityCreated to do all the stuff you need to init listviews etc ...
In your case the problem you have is that you should use the saveInstanceState to keep track of if a fragment is loaded or not because the fragment is re-created on each rotation.
Let's look at some code
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.your_fragment_layout, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
//do nothing if the state already exists
} else {
//do something if state already exists
}
}
Note that if you need to save a given value, for example a boolean you can use
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(YOUR_BOOL_TAG, mYourBooleanVar);
}
and get it back in the onCreateView by using
mYourBooleanVar= savedInstanceState.getBoolean(YOUR_BOOL_TAG);
same applies to other types also.
EDIT
I didn't quite answered your question, so I put more details. The above code is in the fragment. However for your question, in the activity you need something like that.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
//here is the transaction to load your first fragment
}
}
and your first fragement won't reload each time.
The first time savedInstanceState will be null and you set your default fragment. Then each time you rotate savedInstanceState is not null and your default fragment is not reloaded but the one that is currently present.
Only this code is relevant for you, but I let the code above the EDIT for other people in case it can be useful to them.
I have an app with hierarchy like this:
FragmentTabHost (Main Activity)
- Fragment (tab 1 content - splitter view)
- Fragment (lhs, list)
- Framment (rhs, content view)
- Fragment (tab 2 content)
- Fragment (tab 2 content)
All fragment views are being inflated from resources.
When the app starts everything appears and looks fine. When I switch from the first tab to another tab and back again I get inflate exceptions trying to recreate tab 1's views.
Digging a little deeper, this is what's happening:
On the first load, inflating the splitter view causes its two child fragments to be added to the fragment manager.
On switching away from the first tab, it's view is destroyed but it's child fragments are left in the fragment manager
On switching back to the first tab, the view is re-inflated and since the old child fragments are still in the fragment manager an exception is thrown when the new child fragments are instantiated (by inflation)
I've worked around this by removing the child fragments from the fragment manager (I'm using Mono) and now I can switch tabs without the exception.
public override void OnDestroyView()
{
var ft = FragmentManager.BeginTransaction();
ft.Remove(FragmentManager.FindFragmentById(Resource.Id.ListFragment));
ft.Remove(FragmentManager.FindFragmentById(Resource.Id.ContentFragment));
ft.Commit();
base.OnDestroyView();
}
So I have a few questions:
Is the above the correct way to do this?
If not, how should I be doing it?
Either way, how does saving instance state tie into all of this so that I don't lose view state when switching tabs?
I'm not sure how to do this in Mono, but to add child fragments to another fragment, you can't use the FragmentManager of the Activity. Instead, you have to use the ChildFragmentManager of the hosting Fragment:
http://developer.android.com/reference/android/app/Fragment.html#getChildFragmentManager()
http://developer.android.com/reference/android/support/v4/app/Fragment.html#getChildFragmentManager()
The main FragmentManager of the Activity handles your tabs.
The ChildFragmentManager of tab1 handles the split views.
OK, I finally figured this out:
As suggested above, first I changed the fragment creation to be done programatically and had them added to the child fragment manager, like so:
public override View OnCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstance)
{
var view = inflater.Inflate(Resource.Layout.MyView, viewGroup, false);
// Add fragments to the child fragment manager
// DONT DO THIS, SEE BELOW
var tx = ChildFragmentManager.BeginTransaction();
tx.Add(Resource.Id.lhs_fragment_frame, new LhsFragment());
tx.Add(Resource.Id.rhs_fragment_frame, new RhsFragment());
tx.Commit();
return view;
}
As expected, each time I switch tabs, an extra instance of Lhs/RhsFragment would be created, but I noticed that the old Lhs/RhsFragment's OnCreateView would also get called. So after each tab switch, there would be one more call to OnCreateView. Switch tabs 10 times = 11 calls to OnCreateView. This is obviously wrong.
Looking at the source code for FragmentTabHost, I can see that it simply detaches and re-attaches the tab's content fragment when switching tabs. It seems the parent Fragment's ChildFragmentManager is keeping the child fragments around and automatically recreating their views when the parent fragment is re-attached.
So, I moved the creation of fragments to OnCreate, and only if we're not loading from saved state:
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
if (savedInstanceState == null)
{
var tx = ChildFragmentManager.BeginTransaction();
tx.Add(Resource.Id.lhs_fragment_frame, new LhsFragment());
tx.Add(Resource.Id.rhs_fragment_frame, new RhsFragment());
tx.Commit();
}
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstance)
{
// Don't instatiate child fragments here
return inflater.Inflate(Resource.Layout.MyView, viewGroup, false);
}
This fixed the creation of the additional views and switching tab's basically worked now.
The next question was saving and restoring view state. In the child fragments I need to save and restore the currently selected item. Originally I had something like this (this is the child fragment's OnCreateView)
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance)
{
var view = inflater.Inflate(Resource.Layout.CentresList, container, false);
// ... other code ommitted ...
// DONT DO THIS, SEE BELOW
if (savedInstance != null)
{
// Restore selection
_selection = savedInstance.GetString(KEY_SELECTION);
}
else
{
// Select first item
_selection =_items[0];
}
return view;
}
The problem with this is that the tab host doesn't call OnSaveInstanceState when switching tabs. Rather the child fragment is kept alive and it's _selection variable can be just left alone.
So I moved the code to manage selection to OnCreate:
public override void OnCreate(Bundle savedInstance)
{
base.OnCreate(savedInstance);
if (savedInstance != null)
{
// Restore Selection
_selection = savedInstance.GetString(BK_SELECTION);
}
else
{
// Select first item
_selection = _items[0];
}
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance)
{
// Don't restore/init _selection here
return inflater.Inflate(Resource.Layout.CentresList, container, false);
}
Now it all seems to be working perfectly, both when switching tabs and changing orientation.
My Android application has an ActionBar that changes which Fragment occupies a certain FrameLayout. I am trying to use onSaveInstanceState to save the state of a Fragment when the tab is changed, so that it can be recovered in onCreateView.
The problem is, onSaveInstanceState is never called. The Fragment's onDestroyView and onCreateView methods are called, but the Bundle supplied to onCreateView remains null.
Can someone please explain to me when onSaveInstanceState is actually called, how I can make sure it gets called when switching tabs, or the best practice for saving and restoring the state of a Fragment when it is detached and re-attached?
Fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.event_log, container, false);
// Retrieve saved state
if (savedInstanceState != null){
System.out.println("log retrieved");
} else {
System.out.println("log null");
}
return view;
}
#Override
public void onSaveInstanceState(Bundle outState) {
System.out.println("log saved");
super.onSaveInstanceState(outState);
// more code
}
Activity:
/**
* Detach the current Fragment, because another one is being attached.
*/
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (tab.getText().equals(getString(R.string.tab_events))){
if (frEventLog != null) {
ft.detach(frEventLog);
}
}
Fragment#onSaveInstanceState is only called when the Activity hosting the Fragment is destroyed AND there is a chance that you can come back to the same activity AND the fragment is still added to the FragmentManager. The most common case would be screen rotation.
I think your Fragment will also need to do setRetainInstance(true) in onCreate for example. Not exactly sure about that point though.
You should also see this method being called when you press the home button for example. That will destroy the activity but you can go back to it by using the task list for example.
If you just detach() the fragment all you need to do to get it back is to ask the FragmentManager for it.
There are two examples you should have a look at:
ActionBar FragmentTabs and TabHost FragmentTabs
The TabHost example uses
ft.add(containerId, fragment, tag);
// later
fragment = mActivity.getSupportFragmentManager().findFragmentByTag(tag);
to find the instances of previously added Fragments, works until you remove() a Fragment
Regarding onCreateView / onDestroyView: That is called once a fragment gets detached because the next time you attach it needs to create a new View. Note that Fragment#onDetached() is not called when you detach() the fragment because it is still attached to the Activity. It is only detached from the view-hierarchy.
There is another nice example on how to retain fragment state / how to use fragments to retain state in Android Training - Caching Bitmaps.
That example is missing a critical line though:
public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
if (fragment == null) {
fragment = new RetainFragment();
fm.beginTransaction().add(fragment, TAG).commit(); // << add this
}
return fragment;
}
I have a problem reloading an activity with tabs and fragments when I change the orientation of my device.
Here's the situation:
I have an activity which has 3 tabs in the action bar. Each tab loads a different fragment in a FrameLayout in main view. Everything works fine if I don't change the orientation of the device. But when I do that Android tries to initialize the currently selected fragment twice which produce the following error:
E/AndroidRuntime(2022): Caused by: android.view.InflateException: Binary XML file line #39: Error inflating class fragment
Here's the sequence of steps that produce the error:
I load the activity, select tab nr 2. and change the orientation of the device.
Android destroys the activity and the instance of the fragment loaded by tab nr 2 (from now on, 'Fragment 2'). Then it proceeds to create new instances of the activity and the fragment.
Inside Activity.onCreate() I add the first tab to the action bar. When I do that, this tab gets automatically selected. It may represent a problem in the future, but I don't mind about that now. onTabSelected gets called and a new instance of the first fragment is created and loaded (see code below).
I add all the other tabs without any event being triggered, which is fine.
I call ActionBar.selectTab(myTab) to select Tab nr 2.
onTabUnselected() gets called for the first tab, and then onTabSelected() for the second tab. This sequence replaces the current fragment for an instance of Fragment 2 (see code below).
Next, Fragment.onCreateView() is called on Fragment 2 instance and the fragment layout gets inflated.
Here is the problem. Android Calls onCreate() and then onCreateView() on the fragment instance ONCE AGAIN, which produces the exception when I try to inflate (a second time) the layout.
Obviously the problem is Android is initializing the fragment twice, but I don't know why.
I tried NOT selecting the second tab when I reaload the activity but the second fragment gets initialized anyway and it is not shown (since I didn't select its tab).
I found this question: Android Fragments recreated on orientation change
The user asks basically the same I do, but I don't like the chosen answer (it's only a workaroud). There must be some way to get this working without the android:configChanges trick.
In case it's not clear, what I want to know how whether to prevent the recreation of the fragment or to avoid the double initialization of it. It would be nice to know why is this happening also. :P
Here is the relevant code:
public class MyActivity extends Activity implements ActionBar.TabListener {
private static final String TAG_FRAGMENT_1 = "frag1";
private static final String TAG_FRAGMENT_2 = "frag2";
private static final String TAG_FRAGMENT_3 = "frag3";
Fragment frag1;
Fragment frag2;
Fragment frag3;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// my_layout contains a FragmentLayout inside
setContentView(R.layout.my_layout);
// Get a reference to the fragments created automatically by Android
// when reloading the activity
FragmentManager fm = getFragmentManager();
this.frag1 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_1);
this.frag2 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_2);
this.frag3 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_3)
ActionBar actionBar = getActionBar();
// snip...
// This triggers onTabSelected for the first tab
actionBar.addTab(actionBar.newTab()
.setText("Tab1").setTabListener(this)
.setTag(MyActivity.TAG_FRAGMENT_1));
actionBar.addTab(actionBar.newTab()
.setText("Tab2").setTabListener(this)
.setTag(MyActivity.TAG_FRAGMENT_2));
actionBar.addTab(actionBar.newTab()
.setText("Tab3").setTabListener(this)
.setTag(MyActivity.TAG_FRAGMENT_3));
Tab t = null;
// here I get a reference to the tab that must be selected
// snip...
// This triggers onTabUnselected/onTabSelected
ab.selectTab(t);
}
#Override
protected void onDestroy() {
// Not sure if this is necessary
this.frag1 = null;
this.frag2 = null;
this.frag3 = null;
super.onDestroy();
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Fragment curFrag = getFragmentInstanceForTag(tab.getTag().toString());
if (curFrag == null) {
curFrag = createFragmentInstanceForTag(tab.getTag().toString());
if(curFrag == null) {
// snip...
return;
}
}
ft.replace(R.id.fragment_container, curFrag, tab.getTag().toString());
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft)
{
Fragment curFrag = getFragmentInstanceForTag(tab.getTag().toString());
if (curFrag == null) {
// snip...
return;
}
ft.remove(curFrag);
}
private Fragment getFragmentInstanceForTag(String tag)
{
// Returns this.frag1, this.frag2 or this.frag3
// depending on which tag was passed as parameter
}
private Fragment createFragmentInstanceForTag(String tag)
{
// Returns a new instance of the fragment requested by tag
// and assigns it to this.frag1, this.frag2 or this.frag3
}
}
The code for the Fragment is irrelevant, it just returns an inflated view on onCreateView() method override.
I got a simple answer for that:
Just add setRetainInstance(true); to the Fragment's onAttach(Activity activity) or onActivityCreated(Bundle savedInstanceState).
These two are call-backs in the Fragment Class.
So basically, what setRetainInstance(true) does is:
It maintains the state of your fragment as it is, when it goes through:
onPause();
onStop();
It maintains the instance of the Fragment no matter what the Activity goes through.
The problem with it could be, if there are too many Fragments, it may put a strain on the System.
Hope it helps.
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
setRetainInstance(true);
}
Open for Correction as always. Regards, Edward Quixote.
It seems that, when the screen is rotated and the app restarted, it is recreating each Fragment by calling the default constructor for the Fragment's class.
I have encountered the same issue and used the following workaround:
in the fragment's onCreateView begining of:
if (mView != null) {
// Log.w(TAG, "Fragment initialized again");
((ViewGroup) mView.getParent()).removeView(mView);
return mView;
}
// normal onCreateView
mView = inflater.inflate(R.layout...)
I think this is a fool proof way to avoid re-inflating of the root view of the fragment:
private WeakReference<View> mRootView;
private LayoutInflater mInflater;
/**
* inflate the fragment layout , or use a previous one if already stored <br/>
* WARNING: do not use in any function other than onCreateView
* */
private View inflateRootView() {
View rootView = mRootView == null ? null : mRootView.get();
if (rootView != null) {
final ViewParent parent = rootView.getParent();
if (parent != null && parent instanceof ViewGroup)
((ViewGroup) parent).removeView(rootView);
return rootView;
}
rootView = mFadingHelper.createView(mInflater);
mRootView = new WeakReference<View>(rootView);
return rootView;
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
mInflater=inflater!=null?inflater:LayoutInflater.from(getActivity());
final View view = inflateRootView();
... //update your data on the views if needed
}
add
android:configChanges="orientation|screenSize"
in the manifest file
To protect activity recreate try to add configChanges in your Activity tag (in manifest), like:
android:configChanges="keyboardHidden|orientation|screenSize"
My code was a little different, but I believe our problem is the same.
In the onTabSelected I didn't use replace, I use add when is the first time creating the fragment and attach if isn't. In the onTabUnselected I use detach.
The problem is that when the view is destroyed, my Fragment was attached to the FragmentManager and never destroyed. To solve that I implemented on the onSaveInstanceBundle to detach the fragment from the FragmentManager.
The code was something like that:
FragmentTransition ft = getSupportFragmentManager().begin();
ft.detach(myFragment);
ft.commit();
In the first try I put that code in the onDestroy, but I get a exception telling me that I couldn't do it after the onSaveInstanceBundle, so I moved the code to the onSaveInstanceBundle and everything worked.
Sorry but the place where I work don't allow me to put the code here on StackOverflow. This is what I remember from the code. Feel free to edit the answer to add the code.
I think you are facing what I faced. I had a thread downloader for json which starts in onCreate() , each time I changed the orientation the thread is called and download is fired. I fixed this using onSaveInstance() and onRestoreInstance() to pass the json response in a list, in combination of checking if the list is not empty, so the extra download is not needed.
I hope this gives you a hint.
I solved this problem by using below code.
private void loadFragment(){
LogUtil.l(TAG,"loadFragment",true);
fm = getSupportFragmentManager();
Fragment hf = fm.findFragmentByTag("HOME");
Fragment sf = fm.findFragmentByTag("SETTING");
if(hf==null) {
homeFragment = getHomeFragment();// new HomeFragment();
settingsFragment = getSettingsFragment();// new Fragment();
fm.beginTransaction().add(R.id.fm_place, settingsFragment, "SETTING").hide(settingsFragment).commit();
fm.beginTransaction().add(R.id.fm_place, homeFragment, "HOME").commit();
activeFragment = homeFragment;
}else{
homeFragment = hf;
settingsFragment = sf;
activeFragment = sf;
}
}
Initiate this method in OnCreate();