Can I use savedInstanceState() to save the state when removing a fragment, then restore the state when I pop the fragment off the back stack? When I restore the fragment from the back stack, savedInstanceState bundle is always null.
Right now, the app flow is: fragment created -> fragment removed (added to back stack) -> fragment restored from back stack (savedInstanceState bundle is null).
Here is the relevant code:
public void onActivityCreated(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
Long playlistId = bundle.getLong(Constants.PLAYLIST_ID);
int playlistItemId = bundle.getInt(Constants.PLAYLISTITEM_ID);
if (savedInstanceState == null) {
selectedVideoNumber = playlistItemId;
} else {
selectedVideoNumber = savedInstanceState.getInt("SELECTED_VIDEO");
}
}
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(Constants.SELECTED_VIDEO, selectedVideoNumber);
}
I think the problem is that onSavedInstanceState() is never called when being removed and being added to back stack. If I cant use onsavedInstanceState(), is there another way to fix this?
onSaveInstanceState is (unfortunately) not called in normal back-stack re-creation of a fragment. Check out http://developer.android.com/guide/components/fragments.html#Creating and the answer on How can I maintain fragment state when added to the back stack?
I like to store the View I return in onCreateView as a global variable and then when I return I simply check this:
if(mBaseView != null) {
// Remove the view from the parent
((ViewGroup)mBaseView.getParent()).removeView(mBaseView);
// Return it
return mBaseView;
}
The problem is that the fragment needs to have an Id or Tag associated with it in order for the FragmentManager to keep track of it.
There are at least 3 ways to do this:
In xml layout declare an Id for your fragment:
android:id=#+id/<Id>
If your fragments container View has an Id, use FragmentTransaction:
FragmentTransaction add (int containerViewId, Fragment fragment)
If your fragment is not associated with any View (e.g. headless fragment), give it a Tag:
FragmentTransaction add (Fragment fragment, String tag)
Also, see this SO answer.
FWIW, I hit this as well, but in my case onSaveInstanceState was called properly and I pushed in my state data when a new activity fragment was brought up on the smartphone. Same as you, the onActivityCreated was called w/ savedInstanceState always null. IMHO, I think it's a bug.
I worked around it by creating a static MyApplication state and putting the data there for the equivalent of "global variables"...
Related
I am working on an application and there is one specific thing that is bothering me. Let's just say I have one activity and 2 fragments.FragmentA and FragmentB and FragmentA gets attached when activity starts.
I want to save the fragment data and fragment state when orientation changes occur.I have successfully saved fragment data using OnSavedInstanceState method. Now I want to save fragment state in the activity so that if orientation change occurs I want to be on the fragment I was (in my case either FragmentA or FragmentB depends on which was showing before config changes occur).
This is how I am saving the fragment state in the Activity:
#Override
protected void onSaveInstanceState(Bundle outState) {
// Save the values you need into "outState"
super.onSaveInstanceState(outState);
outState.putLong(SS_DATE, userDate.getTime());
android.support.v4.app.FragmentManager manager = getSupportFragmentManager();
Fragment currentFragment = this.getSupportFragmentManager().findFragmentById(R.id.content_container);
manager.putFragment(outState, "currentFragment", currentFragment);
}
And this is how I am retrieving on which fragment I was when the orientation change occurred:
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
FragmentManager manager = getSupportFragmentManager();
#SuppressLint("CommitTransaction")
FragmentTransaction transaction = manager.beginTransaction();
if (savedInstanceState != null) {
Fragment MyFragment = (Fragment) manager.getFragment(savedInstanceState, "currentFragment");
if (MyFragment instanceof FragListStudentsAttendance) {
Log.v("onRestore", FragListStudentsAttendance.TAG);
}else if (MyFragment instanceof FragGetClassesForAttendance){
Log.v("onRestore", FragGetClassesForAttendance.TAG);
if(MyFragment!=null) {
mFragGetClassesForAttendance = (FragGetClassesForAttendance) MyFragment;
}else{
mFragGetClassesForAttendance = new FragGetClassesForAttendance();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// mFragGetClassesForAttendanceNew.setRetainInstance(true);
// transaction.replace(R.id.content_ssadmin_container, mFragGetClassesForAttendanceNew, "FragGetClassesForAttendance").addToBackStack(null);
transaction.add(R.id.content_ssadmin_container, mFragGetClassesForAttendance, FragGetClassesForAttendance.TAG);
//transaction.replace(R.id.newEnrollmentMainContainer, mFragNewEnrollmentResults).addToBackStack("FragNewEnrollments");
transaction.commit();
mFragGetClassesForAttendance.setDate(userDate);
}
}
}
}
Now
Scenario 1:
If I am on fragment A and I rotate the device every thing works fine as it should. Like fragment have web services which loads the data into listview so I check if data exist then there is no need to run the web service and that working for now
Scenario 2:
If I am on fragment B and orientation change occurs everything works fine as it is supposed to be on fragment B. Now When I press back button Fragment A gets called again and all the data also comes from service. I think this shouldn't happen because it was supposed to be in BackStack and it's data was saved. So what Should I do now here?
Scenario 3: On FragmentB I have noticed that when I rotates the device the saveInstanceState function of FragmentA also gets called. Why it is so? where as I was replacing the FragmentB with FragmentA ?
Some Confusions:
Let me talk about some of the confusions also , maybe someone clear it to me although I have searched and read a lot about fragment and activity life cycle,
Actually I want to save the data per activity and fragment on device rotation. I know how to do it with activity(how to save states) so I also know how to do it in the fragment (save state of fragment views) now I am confused how to tell activity which fragment was showing and which to go after config changes(rotation) ? also what happens to FragmentA if I am on FragmentB Does its get attach and detach again and again in background?
I got your problems and confusions. I think the life cycle of fragment is confusing you. and indeed it will confuse you.
You need to learn different situations.
1. Fragment Life cycle when it is in foreground (attaching and detaching with activity) . Please keenly observe all the methods that will call i.e OnSaveInstance,onCreateView,OnDestroyView,onDestroy
2. Fragment life cycle when it is in background (observe the methods stated above)
3. Fragment life cycle when it is added to backstack (and not in foreground)
I am quite sure you are confused with the point number 3. As when the fragment is added to backstack it never gets destroy. So rotating device twice will set the ffragment data to null. I think you are restoring data on ActivityCreated or on onViewCreated ,
Ill suggest you to restore the fragment data in the oncreate. this will work for you when your fragment is coming back to foreground from the backstack .
Example
private List<String> mCountries;</pre>
#Override
public void onCreate(Bundle savedInstanceState)
{
if (savedInstanceState != null)
{
// Populate countries from bundle
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_countries, container, false);
if (mCountries == null)
{
// Populate countries by calling AsyncTask
}
return view;
}
public void onSaveInstanceState(Bundle outState)
{
// Save countries into bundle
}
Hope this will clear your confusions.
I'm having a little trouble understanding the behavior of fragments inside an activity. Consider the following scenario: I have a holder activity and 2 or more fragments inside.
The onCreate method for the activity is like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_holder);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction().add(R.id.container, new Frag1(), "ZZZ").commit();
}
}
I have a button in Frag1 which is linked to a callBack in the activity:
#Override
public void bam(String s) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction beginTransaction = fragmentManager.beginTransaction();
beginTransaction.replace(R.id.container, new Frag2());
beginTransaction.addToBackStack(null);
beginTransaction.commit();
}
At this point Frag2 is on the stack and the only visible Fragment. I used replace and addToBackStack because I need the back navigation.
My problem is that when I rotate the screen while inside Frag2, the super.onCreate(savedInstanceState) method from the activity calls the constructor for Frag1.
Is there any way to avoid the call to Frag1's constructor until the user presses the back button?
Fragments added to the backstack stay in memory and cannnot be garbage collected. They are kept as actual references to fragments. The reason it is recreated is because you still have an instance of the fragment. You can still call it's methods and fields as you can with any other object; it's simply not visible to the user and trying to manipulate its views may fail.
If the only purpose of adding the fragment to the backstack is navigation, this can be accomplished by not putting the fragment in the backstack to beging with, thus letting that instance of the fragment fall out of memory, then by overriding the onBackPressed() in the activity you can re-create() your fragment 1. You are free to cache any data you need as well.
The purpose of the backstack is to preserve the fragments state. When it's written to the backstack onDestroyView() is called, but it's viewHierarchy is saved with onSaveInstancestate(). This saves stuff like text in TextViews, scroll positions, etc.
If there's resource intensive stuff in Fragment 1's initialization you can also try moving it to a later lifecycle event, like onResume().
You can set properties for activity in manifest file so that your activity wont get destroyed on configuration change like as below :
<activity
android:name=".HomeActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustPan|adjustResize" >
</activity>
android:configChanges="keyboardHidden|orientation|screenSize" ; these are the properties.
Or you can do a cross check, by matching tags of fragment, while adding or replacing fragments.For this you need to code as mention below :
1) Adding tag while adding fragment :
getFragmentManager().beginTransaction().add(R.id.container, new Frag1(), "TAG NAME").commit();
2) Then check for existing fragment in onCreate() of activity as below :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_holder);
fragment1 = getSupportFragmentManager().findFragmentByTag("TAG NAME");
if(fragment1 == null) { //if fragment null, then add fragment
getFragmentManager().beginTransaction().add(R.id.container, new Frag1(), "TAG NAME").commit();
}
}
I have an Activity with a FrameLayout and need to show different fragments based on user input.
The code I use for showing a fragment is this:
private void showFragment(Fragment fragment, Bundle args, boolean addToBackStack) {
if (args != null) {
fragment.setArguments(args);
}
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.activity_open_translate, R.anim.activity_close_scale);
fragmentTransaction.replace(R.id.main_frame, fragment);
if (addToBackStack) {
fragmentTransaction.addToBackStack(fragment.getClass().getName());
}
fragmentTransaction.commit();
}
This is called as :
if (contactPickFragment == null) {
contactPickFragment = new ContactPickFragment();
}
showFragment(contactPickFragment, args, true);
All this works fine. Now if the user goes into one fragment presses back and returns back to the same fragment, all my views inside stay the same. For example, I have an EditText inside the fragment and the user edits something inside. If the user comes back to the fragment, the same text persists. I do not want this to happen. How do I reset everything in the view?
I have added code within the Fragment's onCreateView() to clear the text, and from debugging I see that this is being called, but the text never gets cleared. What am I doing wrong here?
If you don't want the data from the previous instance to appear, simply create a new instance of ContactPickFragment each time you show it.
Clearing data in onCreateView() has no effect because view state is restored AFTER onCreateView(). Your Fragment has no view before onCreateView() and so Android cannot possibly apply the previous state any earlier. Values set on the views during onCreateView() will be overwritten by their previous values.
As a general answer, there is no way to "refresh" the view of a Fragment, other than replacing the fragment with another instance of itself (possibly initialized with the parameters that you want to refresh/update).
You can reuse your fragments and refresh the state of your views. You just can't do it from onCreateView as #antonyt correctly points out.
Instead, override onViewStateRestored and set up the state of your views the way you'd like from there.
Something like:
#Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
View view = getView();
// Code to call view.findViewById to grab the views you want
// and set them to a specific state goes here
}
There are advantages to reusing fragments. Not the least of which is that if you have a memory leak with your fragment (which is easier than you may think to accomplish,) you will exacerbate the problem by creating myriads of them.
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'll try to explain my problem:
all my fragments are using setRetainInstance(true)
In my activity onCreate I'm doing this:
if (savedInstanceState == null) {
fragment = onCreatePane();
fragment.setArguments(intentToFragmentArguments(getIntent()));
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.add(R.id.root_container, fragment, getFragmentTag());
trans.commit();
} else {
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
if ( fragment == null ) {
fragment = getSupportFragmentManager().findFragmentByTag(getFragmentTag());
}
if (fragment == null) {
fragment = onCreatePane();
fragment.setArguments(intentToFragmentArguments(getIntent()));
}
trans.add(R.id.root_container, fragment, getFragmentTag());
trans.commit();
}
So when I create the activity and savedInstance is null I create the Fragment, I set it's arguments, I begin the transaction and add my fragment to the transaction with it's own tag (to get it back later).
The user interact with the activity and change the orientation. The activity is destroyed and recreated (as normal activity lifelycle). So now it enter the else, the fragment is null and I do a fragment = getSupportFragmentManager().findFragmentByTag(getFragmentTag()); that returns the correct Fragment holded by the fragmentManager.
The problem is that this fragment hold a reference to the old Activity that has been destoyed so if I do a fragment.getActivity it returns null. How can I update the fragment reference to the acitivy to the new re-created Activity?
UPDATE: To be more precise I'm on the SearchActivity that call the onNewIntent when it get a new Search. So the actual interaction is this-> user do a search -> search is displayed correctly -> user change orientation, result is displayed correctly (if user interact with results they are fine) -> user do a new search from the search button and this call the SearchActivity's onNewIntent that dispatch the new intent to the fragment that has the search logic. Here it crashes because the reference to the activity is null
When and where are you calling getActivity()? The activity reference does get updated automatically, but not immediately. You should be safe to access it after onActivityCreated() was called.
just remove null check from else part if ( fragment == null ) { fragment = getSupportFragmentManager( ...... let update fragment with new one created on orientation change ......
If setRetainInstance(true) -- yes, it's the solution (to find fragment by tag, because instantiate it again -- is the wrong way -- there could be background routines in progress). Otherwise, if you have setRetainInstance(false) the problem can be back.
I had the similar one. I my case -- because I used Loader (in background). Solution was simple: to destroy fragment's loaders in onDestroy() method of the fragment.
In your baseAcvivity you can override onSaveInstanceStat to resolve this problem :
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//solution of fragment.getActivity() is null
outState.remove("android:support:fragments");
}