Saving and restoring ListView with Custom List Adapter
Asked Answered
S

2

5

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?

Stomatal answered 23/3, 2015 at 0:24 Comment(8)
Look up how to use save and restore instance state. It's part of the Activity and Fragment life cycle and can be found in the official android API reference. See: developer.android.com/training/basics/activity-lifecycle/… (this is for an Activity but it applies to Fragments too)Digraph
I may not understand your issue. You're using a custom adapter but that is not related how you are saving the Listview data. Can you simply save your ArrayList in the cache until app exits? Or do you need a database for the Listview data to save them even after app exits?Synergistic
@TheOriginalAndroid I am retrieving the data for the listview from a server so that is not the issue. This listview can be modified by the user via button clicks etc. It is essentially a shopping cart. I want to ensure that these modifications to the listview are saved and can be restored. Is it possible to preserve the state of the entire listview?Stomatal
The main state in my opinion is the row position and sort order of the Listview. But if you want something more extensive, look at Serializable, developer.android.com/reference/java/io/Serializable.html . If you choose this design, tag your question with Serializable.Synergistic
Can you post your adapter code? Or at least the relevant code to show how you track the modifications to the text, buttons, etc in the adapter.Deathwatch
@JaySoyer I've added some code showing a part of what I'm changing in the adapter.Stomatal
So then...is it the data within the adapter itself that gets mutated which changes how the ListView displays to the user? I'm assuming you populate qty text field with such data.Deathwatch
@JaySoyer so based on user interaction with the listview I modify what is shown to the user (qty, price etc). So yep, the data within the adapter itself is what gets mutated. This is done via on click listeners. The plan is to store this modified data and then send it to the server for processing etc. I'm trying to figure out a flow of logic to handle this as well as retain the changes to the listview so that a user can 'back out' of this view and then come back and see everything as they left it (mutated data intact). I hope that makes sense?Stomatal
S
0

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...
Stomatal answered 29/3, 2015 at 11:14 Comment(1)
Does this perform well even when the list is say 500 items?Fatshan
D
11

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.

Deathwatch answered 25/3, 2015 at 1:19 Comment(4)
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?Stomatal
As an addendum, there is some good info on the issue here: #15935822Stomatal
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!Stomatal
be awere that latest android have limitation 1mb for parcelableManufacture
S
0

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...
Stomatal answered 29/3, 2015 at 11:14 Comment(1)
Does this perform well even when the list is say 500 items?Fatshan

© 2022 - 2024 — McMap. All rights reserved.