androidandroid-fragmentsandroid-listviewandroid-dialogfragmentlow-memory

saving instance state on memory low situations


i have an alert dialog wrapped around by a DialogFragment class. the dialog has custom layout in which i show a list of items (i use a ListView with an appropriate adapter). the list of items is pretty huge and important, so i save it inside a Fragment class in my DialogFragment's onPause(), just like in this example.

everything works perfectly, except one very rare situation: when i leave the app with the dialog window open (by pressing the "home" button) and the device gets low on memory, it kills my app's process. when i come back to my application later, it tries to recreate the dialog, but crashes (i get a NullPointerException inside my dialog's ListView adapter), because it cannot restore the list of items that was shown in my left open dialog. this happens because the Fragment in which i store the dialog's state is also destroyed when system kills my app's process so my DialogFragment cannot recreate its dialog as it has no source of data for its list adapter.

what i want from you is to offer me a better way to save the state of my dialog window so it could be recreated even after my app process is killed or to show me a way to cancel the dialog if the list of items is null before it gets resumed (i mean to cancel the dialog somewhere during its recreation). i don't consider a database a good way to store that data, as it would take too long. i'd prefer a way to stop the dialog's recreation if there is no information for its adapter.

UPDATE:

implementing Parcelable to save your objects during the application's lifecycle is really a splendid idea. writing objects to Parcel is fast (trust me, you won't see any delay between lifecycle changes until you start using it inappropriately), and the process of saving and restoring your app's instance state is governed by ActivityManager process, so even if not only your Activity, but also the process hosting your whole application is destroyed by the Android system, ActivityManager maintains the Parcel where your objects were saved in and restores their state if Activity is being recreated from scratch in a new process. big thanks to @Naveed for encouraging me to try this practice!

NOTE FOR THOSE WHO STILL DOESN'T CATCH WHAT SAVING INSTANCE STATE REALLY IS ABOUT:

saving instance state of your Activity along its lifecycle events means maintaining the changes of information that are meaningful only for your application's CURRENT UI or user's CURRENT progress, but are not essential to your application or users when they explicitly stop the app (by pressing the back button) and start it again later. this particular form of information that is really temporary should be saved within the onSaveInstanceState() callback method in the Bundle object by using the put...() methods for primitive Java types or implementing the Parcelable interface in your custom classes, and later restored in onCreate() or onRestoreInstanceState() callbakcs.

all the other data that is important for the users no matter whether they explicitly close your application or leave it just for a short time should be saved in a persistent storage, such as an SQLite database or a SharedPreferences system so it may be restored whenever the user or your application needs its previous progress again. you should save this form of data within onPause() callback method, because it is the last method guaranteed to be called before the system is able to destroy your app's process in case it needs to recover some resources. if you choose to save the data in a method called after the onPause(), i.e. onStop() or onDestroy(), you are in a huge risk of not getting the data saved, because those methods might never get called if the system decides to kill your app immediately in some very bad (but not necessarily rare) case.

another thing: please only use SharedPreferences when storing small amount of data, as it's not built for storing lists, collections or bundles. Consider spending just a couple of hours more to implement an SQLite database interface for your application and use it more comfortably to maintain large chunks of information.


Solution

  • The system will call onSaveInstanceState when it is about to kill your app because of memory problems. You can save any state related information in onSaveInstanceState. If your data is a primitive type, Parcelable, or Serializable it can be saved in a bundle.

    When the Activity is recreated you can restore it in onRestoreInstanceState or onCreateor any of the following in the fragment life cycle.

    onCreate(Bundle) called to do initial creation of the fragment.
    
    onCreateView(LayoutInflater, ViewGroup, Bundle) creates and returns the view  hierarchy associated with the fragment. 
    
    onActivityCreated(Bundle) tells the fragment that its activity has    completed its own Activity.onCreate(). 
    
    onViewStateRestored(Bundle)tells the fragment that all of the saved state of its view hierarchy has been restored.
    

    If you simply don't want to show the dialog. Then you can do the null check in onResume and don't create the adapter if data is null and dismiss the dialog if it is visible.

    Update: example parcelable:

    public class MyObject implements Parcelable {
    
            private String myString;
            private int myInt;
    
        protected MyObject(Parcel in) {
            myString = in.readString();
            myInt = in.readInt();
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(myString);
            dest.writeInt(myInt);
        }
    
        @SuppressWarnings("unused")
        public static final Parcelable.Creator<MyObject> CREATOR = new Parcelable.Creator<MyObject>() {
            @Override
            public MyObject createFromParcel(Parcel in) {
                return new MyObject(in);
            }
    
            @Override
            public MyObject[] newArray(int size) {
                return new MyObject[size];
            }
        };
    }