53

I have an ArrayList with custom objects that I would like to be able to save and restore on a screen rotate.

I know that this can be done with onSaveInstanceState and onRestoreInstanceState if I were to make the ArrayList its own class, which implements either Parcelable or Serializable... But is there a way to do this without creating another class?

Vasily Kabunov
  • 6,511
  • 13
  • 49
  • 53
Kalina
  • 5,504
  • 16
  • 64
  • 101

4 Answers4

92

You do not need to create a new class to pass an ArrayList of your custom objects. You should simply implement the Parcelable class for your object and use Bundle#putParcelableArrayList() in onSaveInstanceState() and onRestoreInstanceState(). This method will store an ArrayList of Parcelables by itself.


Because the subject of Parcelables (and Serializables and Bundles) sometimes makes my head hurt, here is a basic example of an ArrayList containing custom Parcelable objects stored in a Bundle. (This is cut & paste runnable, no layout necessary.)

Implementing Parcelable

public class MyObject implements Parcelable {
    String color;
    String number;

    public MyObject(String number, String color) {
        this.color = color;
        this.number = number;
    }

    private MyObject(Parcel in) {
        color = in.readString();
        number = in.readString();
    }

    public int describeContents() {
        return 0;
    }

    @Override
    public String toString() {
        return number + ": " + color;
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeString(color);
        out.writeString(number);
    }

    public static final Parcelable.Creator<MyObject> CREATOR = new Parcelable.Creator<MyObject>() {
        public MyObject createFromParcel(Parcel in) {
            return new MyObject(in);
        }

        public MyObject[] newArray(int size) {
            return new MyObject[size];
        }
    };
}

Save / Restore States

public class Example extends ListActivity {
    ArrayList<MyObject> list;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if(savedInstanceState == null || !savedInstanceState.containsKey("key")) {
            String[] colors = {"black", "red", "orange", "cyan", "green", "yellow", "blue", "purple", "magenta", "white"};
            String[] numbers = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"};

            list = new ArrayList<MyObject>();
            for(int i = 0; i < numbers.length; i++) 
                list.add(new MyObject(numbers[i], colors[i]));
        }
        else {
            list = savedInstanceState.getParcelableArrayList("key");
        }

        setListAdapter(new ArrayAdapter<MyObject>(this, android.R.layout.simple_list_item_1, list));
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putParcelableArrayList("key", list);
        super.onSaveInstanceState(outState);
    }
}
Sam
  • 86,580
  • 20
  • 181
  • 179
  • 1
    Thanks! However, I'm lost on the `private MyObject(Parcel in) { color = in.readString(); number = in.readString(); }` bit... That's easy for Strings, but how do I read my custom arraylist? `in.readArrayList(MyObject.class.getClassLoader())` gives me an error. – Kalina Sep 20 '12 at 16:25
  • 1
    Hmm, in your question you asked about "an ArrayList with custom objects" implying `ArrayList`, which this answer handles in the `Parcelable.Creator`; however your comment just asked "how do I read my custom arraylist" implying `MyArrayList`. What class are you trying to extend? (Sorry to be technical but I want to give you the best answer fastest.) – Sam Sep 20 '12 at 16:39
  • Sorry, I do mean ArrayList – Kalina Sep 20 '12 at 18:22
  • 2
    Ok, then you don't need to `readArrrayList()` in your object class. Look at `onCreate()` in the example Activity I posted. I check: if an `ArrayList` doesn't already exist, make it; if it does exist read in the `ArrayList` with `list = savedInstanceState.getParcelableArrayList("key");`. Does this make sense? I can easily comment my example in more detail if it will help. – Sam Sep 20 '12 at 19:02
  • @Sam How would you use Parcelable with an ArrayList of Strings, not custom objects? – Vishwa Iyer Jul 12 '14 at 10:07
  • I tried to implement this solution, but when i call `outState.putParcelableArrayList("key", list);` its not calling MyObject.writeToParcel. Instead, its saving all properties to the bundle automatically – Marlon Jan 30 '17 at 19:26
  • `writeToParcel` isn't allowed for custom lists, so app will crush when press home button for example – user25 Mar 11 '17 at 14:06
  • @Sam hi sir. i have a very big problem for save and restore, could you help me on this topic? http://stackoverflow.com/q/43972147/6596724 – tux-world May 15 '17 at 09:23
  • @Sam So both of these are needed? if(savedInstanceState == null || !savedInstanceState.containsKey("key")) Why is the containsKey() needed if the first "==null" test is satisfied in a test? – AJW Sep 06 '18 at 02:32
3

You can use onRetainNonConfigurationInstance(). It allows you to save any object before an configuration change, and restore it after with getLastNonConfigurationInstanceState().

Inside the activity:

    @Override
    public Object onRetainNonConfigurationInstance() {
        return myArrayList;
    }

Inside onCreate():

    try{
        ArrayList myArrayList = (ArrayList)getLastNonConfigurationInstance();
    } catch(NullPointerException e) {}

Handling Runtime Changes: http://developer.android.com/guide/topics/resources/runtime-changes.html Documentation: http://developer.android.com/reference/android/app/Activity.html#onRetainNonConfigurationInstance%28%29

Techwolf
  • 1,228
  • 13
  • 32
  • Wow, never knew about that function! Super simple and exactly what I needed... However, I see that it is deprecated... – Kalina Sep 20 '12 at 13:58
  • It is, unfortunately, but it still works and there's no other easy way to do this. Sam's answer is the only other way I've found, but it's a lot harder to implement. – Techwolf Sep 20 '12 at 14:43
  • 3
    If you're using Fragments, you can use `Fragment.setRetainInstance(true)` instead, which is the newer version of this (but only works with Fragments). Documentation: http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean) – Techwolf Sep 20 '12 at 15:01
  • That only works if the fragment wasn't added to the backstack unfortunately – Tyler Pfaff Jan 19 '13 at 17:46
2
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    ArrayList<Integer> id=new ArrayList<>();
    ArrayList<String> title=new ArrayList<>();
    for(int i=0;i<arr.size();i++){
        id.add(arr.get(i).id);
        title.add(arr.get(i).title);
    }
    outState.putIntegerArrayList("id",id);
    outState.putStringArrayList("title",title);
}
Hamdy Abd El Fattah
  • 1,405
  • 1
  • 16
  • 16
-4

Yes, you can save your composite object in shared preferences. Let's say:

Student mStudentObject = new Student();
     SharedPreferences appSharedPrefs = PreferenceManager
      .getDefaultSharedPreferences(this.getApplicationContext());
      Editor prefsEditor = appSharedPrefs.edit();
      Gson gson = new Gson();
      String json = gson.toJson(mStudentObject);
      prefsEditor.putString("MyObject", json);
      prefsEditor.commit(); 

and now you can retrieve your object as:

     SharedPreferences appSharedPrefs = PreferenceManager
     .getDefaultSharedPreferences(this.getApplicationContext());
     Editor prefsEditor = appSharedPrefs.edit();
     Gson gson = new Gson();
     String json = appSharedPrefs.getString("MyObject", "");
     Student mStudentObject = gson.fromJson(json, Student.class);
Cleb
  • 25,102
  • 20
  • 116
  • 151
Andriya
  • 241
  • 2
  • 14
  • 2
    Bad idea to do this - depending on the size of the list being saved. SharedPreferences is an XML file, it's not designed for storage of huge data sets. – mwieczorek Aug 12 '16 at 09:17