Saving and restoring ListView with Custom List Adapter

10,904

Solution 1

After doing some reading/research I ended up solving this by saving the adapter data to Saved Preferences by using Gson (https://code.google.com/p/google-gson/).

I used Gson to first convert my array of objects into Json and then stored the Json strings in the Saved Preferences.

Subsequently I was able to retrieve these as required and read the Json into Java objects again, store these in an array list and pass this to my listview adapter.

Here's a brief overview of the process for anyone who is looking to do something similar.

Saving to Shared Preferences:

    SharedPreferences settings;
    Editor editor;
    settings = c.getSharedPreferences(PREFS_NAME,Context.MODE_PRIVATE);
    editor = settings.edit();
    Gson gson = new Gson();
    String ItemsJson = gson.toJson(list);
    editor.putString(categoryId, ItemsJson);
    editor.apply();

Reading from Shared Preferences:

    if (settings.contains(categoryId) {
       String jsonItems = settings.getString(categoryId, null);
       Gson gson = new Gson();
       Item[] favoriteItems = gson.fromJson(jsonItems, Item[].class);
       list = Arrays.asList(favoriteItems);
       ItemsFromSharedPrefs = new ArrayList<>(list);
       AllCategories.addAll(ItemsFromSharedPrefs);
   } etc...

Solution 2

Adapter

You will want your custom adapter to implement some sort of getter for it's internal data. For instance

public ArrayList<YourDataType> getList() {
    return new ArrayList<YourDataType>(mAdapterData);
}

Activity

Then in your activity/fragment, you'll need to save and restore that data.

private static final String STATE_LIST = "State Adapter Data"

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putParcelableArrayList(STATE_LIST, getAdapter().getList());
}

While there is an onRestoreInstanceState() method you could override, I typically restore during onCreate(). Usually more convenient to when other things are getting instantiated. Either or is viable.

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

    //If restoring from state, load the list from the bundle
    if (savedInstanceState != null) {
        ArrayList<YourDataType> list = savedInstanceState.getParcelableArrayList(STATE_LIST);
        ItemAdapter adapter = new MenuItemAdapter(list, getActivity(), this);
    } else {
          //Else we are creating our Activity from scratch, pull list from where ever you initially get it from
          ArrayList<YourDataType> list = getInitData();
          ItemAdapter adapter = new MenuItemAdapter(list, getActivity(), this);
    }
}

YourDataType

You didn't mention what YourDataType was but I'm assuming it's a custom class. In order to work with the bundled savedstate, it must implement Parcelable. Android's dev link on Parcelable and a StackOverFlow post explaining how to write your own custom class with Parcelable.

Update

Depending on what you are doing with the fragment will dictate if the onSavedInstanceState() method will be called. From the way your question is asked, I'm assuming you are pushing one fragment onto the backstack to load another on top. Then hitting the back button will reloaded that fragment from the backstack...and pass along the bundled state to reload from.

Of course that is just one of many scenarios. It's perfectly possible for the fragment in question to only perform onPause() followed by onStop()...then when redisplaying the fragment, only seeing it do onStart() followed by onResume(). In this case, there would not be a saved state because the fragment was never fully destroyed, so there is nothing to restore. It should be in the same state. If it's not and instead you are seeing it reload the initial data...then you are probably reloading the initial data in or after onStart(). You'll want to move all that initializing data to onCreate() instead.

Another possible case is that you completely destroy the fragment without ever putting it on the backstack. Re-initializing it would not have a saved state to pull from. If you wanted to "restore" this fragment back to the state beforehand, then you can not rely upon onSavedInstanceState(). You will need to persist that information manually somewhere in memory, to disk, or a DB yourself. Then pull from it accordingly.

Life cycles with fragments are unfortunately really complex and depend greatly on usage and even between the support library vs native fragments.

Share:
10,904
Osborne Cox
Author by

Osborne Cox

Currently enjoying learning Android/Java programming. Also dabbling with some C#/WPF/XAML in Visual Studio 2013.

Updated on June 12, 2022

Comments

  • Osborne Cox
    Osborne Cox almost 2 years

    I'm currently using a custom list adapter and modifying the rows of the listview at runtime (changing text, buttons etc).

    I want to find a way to save/restore the changes made to the listview at runtime when I start another fragment and then come back to the fragment containing the listview.

    Currently the listview is being reloaded every time and all the runtime changes are lost.

    The following shows how I am setting the list adapter.

     public void setAdapterToListView(ArrayList<item> list) {
            ItemList = list;
            ItemAdapter adapter = new MenuItemAdapter(list, getActivity(), this);
            ItemListView.setAdapter(adapter);
        }
    

    I am then using a custom list adapter to make the changes (mentioned above) at runtime. Here's a snippet of how I am changing things at runtime:

    if (holder.qty.getText().toString().equals("Qty")) {
        relativeLayout.setBackgroundColor(row.getResources().getColor(R.color.navDrawerL ightBlue));
        holder.qty.setText("1");
        holder.qty.startAnimation(anim);
        holder.removeItem.setBackgroundResource(R.drawable.remove_item_red);
        holder.removeItem.setEnabled(true);
    
       ...
    

    Is there a recommended way to approach this issue?

  • Osborne Cox
    Osborne Cox about 9 years
    Thanks very much for your suggested solution Jay. I tried implementing this but noticed that onSaveInstanceState was not being called when I 'backed out' of the fragment containing the ListView. onPause() and onStop() get called however. For this reason I'm having trouble saving the Parcleable to the Bundle. After doing some reading it was mentioned that onSaveInstanceState is not part of the Fragment lifecycle and hence it may not be called when required. If this is the case, could a workaround using onPause() be implemented?
  • Osborne Cox
    Osborne Cox about 9 years
    As an addendum, there is some good info on the issue here: stackoverflow.com/questions/15935322/…
  • Osborne Cox
    Osborne Cox about 9 years
    Thanks for your update. I did some further research and followed your advice on finding a way to persist the information instead. It turned out to be easier than managing fragment state. Cheers!
  • Kushan
    Kushan about 7 years
    Does this perform well even when the list is say 500 items?
  • Siarhei
    Siarhei about 5 years
    be awere that latest android have limitation 1mb for parcelable