Fragment state lost when device left idle - android

Fragment losing state and shows an empty UI if left idle for 20 minutes. I'm using FragmentStatePagerAdapter and I have tried calling the notifyDataSetChanged in onStart() of my FragmentActivity.
Kindly help me how to save the object and state of my fragment and reuse it on reload of the app.

Android can kill your app if needed, you need to use onSaveInstanceState to keep your state in this cases. (Remember: Save important data in onPause!)
onSaveInstanceState exists in Activity and Fragments and is used in the same way like an activity
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("integer", anInteger);
}
Now in onCreate, onCreateView or onActivityCreated you have this argument BundlesavedInstanceState which corrisponds to the bundle saved. (Check if it's null too.)
If not enought maybe Android killed your FragmentManager too, so you need to override onSaveInstanceState and onRetoreInstanceState in your Activity and restore the fragment.
Maybe this answer could help you about the last thing i said: Using onSaveInstanceState with fragments in backstack?

A Fragment's life-cycle is closely tied to the Activity's lifecycle. This means, when your Activity goes idle; it will kill off any contained Fragments. To store Fragments you could always retain them in concordance with the Fragment API. This means you will generally be using the Fragment in a background. However the best way to keep a from being destroyed or lost from an Activity's end would be to store relevant information in a custom object and then to recreate the Fragment when the Activity is resumed.
For instance; I could have a custom object that would store relevent UI values for my Fragment and when my Activity either idles or changes I would save those relevant values to my custom object that I created. Then, when either a new Activity is created; or my old Activity is resumed; I would retrieve those values and put them back into my Fragment's UI. Hoped this helped :)

In case android needs memory, it kills the running apps. So you must save the objects using
public void onSaveInstanceState(Bundle savedState) {}
Note that savedState must be serializable.
You must call notifyDataSetChanged() in onResume(), because it ensures that it is called when the activity resumes.
For a detailed answer, please post your code.

Hard to answer without your code.
However I can say that the state is usually saved by the savedInstanceState
Usually in the onActivityCreated you have something like the following. In the example I give I save a boolean of either text was entered or not.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mHasText = savedInstanceState.getBoolean(HAS_TEXT_TAG);
} else {
// do something
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(HAS_TEXT_TAG, mHasText);
}
it's just an example as without your code it's difficult to anwer

Related

ViewPager Fragments getting destroyed while app is in background?

I can't for the life of me figure out why sometimes my Fragments become uncontrollable after my app has been launched, paused, and then resumed.
I hold references to my Fragments like this:
public class MainActivity ... {
public static AccountFragment accountFragment;
....
#Override
protected void onCreate( ... {
accountFragment = new AccountFragment(this);
...
}
I have a custom toolbar with a Spinner which theoretically should allow the user to cause an AsyncTask in the AccountFragment to run. This is implemented like so:
if (accountFragment.getView() != null) {
accountFragment.load()
}
I then create a ViewPager, a FragmentPagerAdapter, etc.
This works completely fine most of the time. The user can select a new item in the Spinner and the AccountFragment will update accordingly. However, sometimes after the app has been stopped and then later resumed, the AccountFragment will not respond at all to anything. Even other views in the app which should affect the AccountFragment are useless. It makes no sense to me because the AccountFragment's view cannot be null, and the AsyncTask which is executed by accountFragment.load() makes some very simple changes to the UI in its onPreExecute which should be very apparent (e.g. Everything disappears and a ProgressBar appears) which is simply not happening.
Any ideas?
There's not enough code to know exactly what's going wrong here, but there are also a bunch of stuff you're doing wrong.
1) Don't store fragment reference in a public static field. Make it just private, or protected at most. Read basic Java manuals for explanation.
2) Don't overload Fragment's constructor, it's discouraged by design. If you need to pass some values to it, do it by using arguments.
3) I guess you're not performing a check during onCreate() method in your Activity if it's being recreated or freshly created. You could end up with two instances of AccountFragment where one of them is in some weird detached state.
...
You should probably spend more time researching basics about Fragment/Activity relationship and how to avoid situations like this.
What you need is override these methods:
onSaveInstanceState(){}
onCreate (Bundle savedInstanceState){}
or onCreateView()
See http://developer.android.com/reference/android/app/Fragment.html#onSaveInstanceState(android.os.Bundle) . You put all the necessary parameters to bundle in onSaveInstanceState() and restore them in onCreate() or onCreateView().

creating a Parcelable which will be used inside the same activity

I've imported some JSON data and fetched it into a custom arrayList. The arraylist looks like this:
ArrayList<HashMap<String, String>> postList;
It all works fine, but if the orientation changes the whole JSON parse/fetch process will start over again. That's why I like to store my custom ArrayList.
I have found a lot of information about (how to use) the Parcelable interface, but they all cover sending data from activity A to B.
Can somebody please provide an example about how to use a Parcelable inside the same activity? Thanks in advance!
You should search online for onSaveInstanceState, it's a callback which is defined inside Activity class.
It works in the same way when you need to pass data between activites
#Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
}
Inside outState you will use normal Bundle methods to pass Strings, Integers and Parcelable values (putParcelableArrayList/putParcelable/putParcelableArray).
When the screen rotate or something happens (activity got destroyed etc.), this method could be called and when the activity gets recreated the bundle you used here will be passed to onCreate inside the savedInstanceState argument (the only argument of onCreate, Bundle).
Here inside onCreate you check if (savedInstanceState != null) to be sure you have a saved state, if it's true you use savedInstanceState.getParcelableArrayList (or something else, depends on what you want to get) to read back the list.
if (savedInstanceState != null)
{
ArrayList<Parcelable> parcelableArrayList = savedInstanceState.getParcelableArrayList("key");
}
#Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putParcelableArray(myParcelableList);
}
It all works fine, but if the orientation changes the whole JSON parse/fetch process will start over again. That's why I like to store my custom ArrayList.
You mentioned that your orientation causes the whole process to restart in your Activity. Are you running the json... etc in your onCreate method? If that's the case, orientation changes cause onCreate to be called.
You can prevent onCreate from being called by modifying your AndroidManifest.xml to handle your own orientation changes for the current activity (look for the android:configChanges):
<activity
android:name="your.activity.package.andclass"
android:configChanges="orientation|screenSize">
<!-- etc -->
You might need to manually handle config changes for certain events, but I've never found it necessary.

Saving state on List Data when App Resumes

Here is my set up.
I have a Main SherlockFragmentActivity. It swaps many ListFragments back and forth with FragmentTransaction's. To indicate loading, anytime a ListFragment loads the data I call:
setSupportProgressBarIndeterminateVisibility(true);
The problem:
When the main Activity mentioned above first starts, or the user leaves and goes to other apps and then restarts this one after an extended period of time, the SherlockFragmentActivity seemingly reloads, there is no progress dialog in the ActionBar, the screen is white for a few seconds, and then the list data repairs (The length depends on the data connection).
Here is some supplemental code: When the main/base Activity first loads, this is one of the first things I do in the onCreate():
// Set up Main Screen
FragmentTransaction t2 = this.getSupportFragmentManager().beginTransaction();
SherlockListFragment mainFrag = new FollowingFragment();
t2.replace(R.id.main_frag, mainFrag);
t2.commit();
FollowingFragment is the one that will always load in this instance. It contains a ListView and an AsyncTask pulling data from a MySQL database.
My question: How do I prevent this delay? And how do I handle maintaining the data when user leaves for extended periods of time?
This is the normal behavior, it happens because your activity has been killed to save memory for other apps, when your app was in the background. And when it comes back to the foreground, the system recreate your activity, which will recreate your fragment.
But if your really want to avoid recreating your fragment, you can use setRetainInstance in your fragment's onCreate method:
public void setRetainInstance (boolean retain)
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being re-created.
onAttach(Activity) and onActivityCreated(Bundle) will still be called.
And use something like this in your FragmentActivity's onActivityCreated method:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FragmentManager fm = getFragmentManager();
// Check to see if we have retained the worker fragment.
mRetainableFragment = (RetainedFragment)fm.findFragmentByTag("fragmentTag");
// If not retained (or first time running), we need to create it.
if (mRetainableFragment == null) {
mRetainableFragment = new RetainedFragment();
// Tell it who it is working with.
mRetainableFragment.setTargetFragment(this, 0);
fm.beginTransaction().add(mRetainableFragment, "fragmentTag").commit();
}
}
But be aware that, this should only be use for headless fragment (fragment without UI, i.e return null in onCreateView, aka worker fragment). You can still use this method for UI fragment though but it is not recommanded by google, in that case the data must be stored as member (field) in your activity. If the data which should be stored is supported by the Bundle class, you can use the onSaveInstanceState() method to place the data in the Bundle, and retrieve that data in the onActivityCreated() method.
Moreover this only works if the fragments is not added to the backstack.
According to the Android developer reference page on Activity, you have to request the progress bar feature before calling setSupportProgressBarIndeterminateVisibility():
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_main);
setSupportProgressBarIndeterminateVisibility(true);
The other issue, reloading the fragments, is due to Android killing your ListFragment so that they have to reload could be resolved by overriding onSaveInstanceState(Bundle outState) and caching your data there to be retrieved in your ListFragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(savedInstanceState != null) {
// retrieve data from Bundle here
} else {
// no data, we need to reload from network
}
// initialize your View here
}
This method is not guaranteed to run all the time, however (it's not in the Fragment lifecycle). As such, you should also make sure you cache the data in onPause() and use it instead of always loading from a network connection.
#Override
public void onPause() {
super.onPause();
SharedPreferences prefs = getActivity().getSharedPreferences();
SharedPreferences.Editor editor = prefs.edit();
// put your data here using editor
editor.commit();
}
Then you can load this data in your onCreateView() by retrieving an instance of SharedPreferences and using prefs.getString(String key) and other methods.
When your app get killed, you lose your activity state and data! There are two scenarios that I can assume about your AsyncTask:
1. you are pulling some data from a Webserver. In this case I personally think caching your data which you retrieved from webserver is a better solution than implementing serializable.
2. You are pulling lots of data from local database (which causes retrieving data to take some time). In this scenario I suggest retrieving only as much data as you need, not more! (for example you can retrieve 20 items, and when user scrolling to the end of ListView retrieve next 20 items).
This solution helps your application retrieve data faster.
PS: To give you a clue how to implement the WebserviceModule with cache capability, which I assume is located in your AsyncTask, you can save every response from webserver in the SDCard and every time you trying to retrieve some resource from webserver, you should check the SDCard to see if your request already sent and cached! For every request, your should make a unique signature base on url and post parameters to recognize cached files.
When you return to activity after extending period of time, the whole app being restarted. So You can't rely on object variables to save data.
So You could avoid delay You've mentioned with saving data to some local storage in activity onStop() method. For example, shared preferences.
And when You call onCreate(), check whether You have data saved and use it if exists (and clean up to have "clean" start next time), otherwise start asynctask.

DialogFragment with setRetainInstanceState(true) is not displayed after the device is rotated

I have a question regarding DialogFragment. I am trying to make a dialog that keeps it's state after the device is rotated. This dialog has a bunch of references to things such as adapters and other heavier objects and I need this to be kept upon rotation, if possible without having to make every reference Parcelable or Serializable in order for me to use onSaveInstanceState to save and restore them when the original activity is re-created.
I've noticed there's a method called setRetainInstance(boolean) on the DialogFragment which allows you to keep the dialog fragment instance when the activity is re-created. However, when I rotate the device now, the dialog is not showing anymore. I know I can get it from the activity's FragmentManager, but I cannot find a way to make it visible again. Any suggestions on this?
Thanks,
Mihai
There are few things you need to do :
use instance factory method to initiate a DialogFragment instance like this :
public static MyDialogFragment newInstance(MyModel model) {
MyDialogFragment myDialogFragment = new MyDialogFragment();
Bundle bundle = new Bundle();
bundle.putSerializable("MODEL", model);
myDialogFragment .setArguments(bundle);
return myDialogFragment;
}
by putting setRetainInstance(true) in onCreate, all of your references declared in the fragment will be kept after the original activity is re-created
#Override
public void onCreate(Bundle icicle) {
this.setCancelable(true);
setRetainInstance(true);
super.onCreate(icicle);
}
avoiding disappear on rotation by doing this
#Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
get your object by using
(MyModel) getArguments().getSerializable("MODEL")
The dialog fragment should be preserved automatically as long as you do the following:
If you call an Activity onSaveInstanceState(), make sure you call the super function!!!!. In my case, that was the key. Also make sure you do the same thing in the Fragment.
If you use setRetainInstance, you need to manually store off the values. Otherwise, you should be able to not worry about it, in most cases. If you're doing something a bit more complicated, you might need to setRetainInstance(true), but otherwise ignore it.
Some people have complained about a bug in the support library, where a dismiss message is sent when it shouldn't be. The latest support library seems to have fixed that, so you shouldn't need to worry about that.

Explain GoogleApi savedInstanceState

In the Google Api Example Code they have a very simple example on saving the state. I need this in order to deal with screen rotation.
My problem is that I do not understand where the methods getSavedText() and setSaveText() get called from. I put the debugger on them but it never gets triggered. The whole thing just works by "magic". What other things can I save? where do I find documentation explaining the magic
public class SaveState extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_save_state);
((TextView)findViewById(R.id.msg)).setText(R.string.hello_world);
}
CharSequence getSavedText() {
return ((EditText)findViewById(R.id.saved)).getText();
}
void setSavedText(CharSequence text) {
((EditText)findViewById(R.id.saved)).setText(text);
}
}
Link
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/SaveRestoreState.html
Alternate link
http://goo.gl/6TJy5
This Api Demo sample is bad one because it is confusing that you are expecting it will override onSaveInstanceState() method than use persisted state in onCreate() or onRestoreInstanceState() when you read the documentation, but it does not.
Firstly getSavedText() and setSavedText() methods are not used in this Save & Restore State activity. They are used/called in LocalSample Instrumentation example.
Forget these two methods you can erase them. Let's come to how this sample works; on background Android calls onRestoreInstanceState() and make all UI views persist their state to Bundle parameter of the method. Please debug the code and after rotating screen inspect savedInstanceState parameter in onCreate() method of this sample. You will see before rotation text of R.id.saved EditText in that Bundle. Main trick is that why one of EditText is saving its state and the other is not is because of freezesText attribute of EditText. freezesText attribute makes EditText/TextView save its state in onSaveInstanceState method. Please look here for more information about freezesText.
delete android:freezesText="true" or make it false from save_restore_state.xml or override onRestoreInstanceState method and do not call super.onRestoreInstanceState() and observe that it is loosing its state after rotation (configuration changes).
Note: You do not have to override onRestoreInstanceState method for this sample but for other requirements to save internal state of the Activity you have to.

Categories

Resources