Good solution to retain listview items when user rotate phone and keep all data in ArrayAdapter
Asked Answered
D

6

42

I'm using Fragment with listView. I fill ArrayAdapter associated with this listview, by data received in custom Loader(from internet). Custom ArrayAdapter supports infinite scrolling(paging).

What is the best way to store items in ArrayAdapter when user rotate device and keep scroll position in ListView?

I'm thinking about creation of non-visual Fragment with ArrayAdapter, and using setRetainInstance method to save values.

Any suggestions for better solution?

Distinctly answered 22/5, 2013 at 13:3 Comment(3)
developer.android.com/guide/topics/resources/…Grunion
you can check this for fragments androiddesignpatterns.com/2013/04/… you can check onConfigurationChanged @ developer.android.com/reference/android/app/Fragment.htmlGrunion
What if you make the ArrayAdapter static, in the onCreate check if it's null, it if it create it else call invalidate on it (to force a redraw)?Efficacious
S
56

To work with the Android framework and Fragment lifecycle you should implement the onSaveInstanceState method in your Fragment. For simplicity I've assumed that you have an array of String values that you can get to (I generally extend ArrayAdapter to encapsulate view construction and to provide a convenience method to access the entire underlying dataset):

public void onSaveInstanceState(Bundle savedState) {

    super.onSaveInstanceState(savedState);

    // Note: getValues() is a method in your ArrayAdapter subclass
    String[] values = mAdapter.getValues(); 
    savedState.putStringArray("myKey", values);

}

You can then retrieve the data in your onCreate method (or onCreateView or onActivityCreated - see the Fragment JavaDoc) like this:

public void onCreate (Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    if (savedInstanceState != null) {
        String[] values = savedInstanceState.getStringArray("myKey");
        if (values != null) {
           mAdapter = new MyAdapter(values);
        }
    }

    ...

}

This ensures that all lifecycle events will be handled properly, without loss of data, including device rotation and the user switching to other applications. The danger of not using onSaveInstanceState and using memory is the danger of Android reclaiming that memory. Saved state would not be affected by this but using instance variables or hidden fragments would result in loss of data.

If savedStateInstance is null then there is no state to restore.

The if (values != null) is simply to guard against the possibility that no array was saved, but if you code your ArrayAdapter to handle a null data set you won't need this.

The ultimate solution, if your rows are instances of one of your own classes and not single data items, is to implement the Parcelable interface on that class, then you can use savedState.putParcelableArray("myKey", myArray). You'd be surprised how useful it is to know how to implement Parcelable - it allows you to pass your classes around inside intents and allows you to write much cleaner code.

Swagger answered 29/5, 2013 at 16:17 Comment(8)
Sounds good to save data, but how I can keep scroll position in ListViewDistinctly
Well ListView has an onSaveInstanceState() method that returns a Parcelable, and an onRestoreInstanceState(Parcelable state) method to restore the state. If I recall correctly, the saved state includes scroll position.Swagger
To retain the scroll position, also save: listView.getFirstVisiblePosition() in the onSaveInstanceState(). After you set your adapter to the listView, just add this line: listView.setSelectionFromTop( index , 0 ); where the index is the index you saved.Urbanist
You could additionally check for safety in the oncreate method if savedInstanceState contains "mykey"Pergrim
@vickey : Nice comment, exactly what i wanted.Arsenic
What if we aren't using an array adapter but a baseAdapter and you are rather passing an arraylist of Object to the adapter constructor?Gadmon
Please solve my problem #35428662Crozier
@VikramGupta what call do you use to get the position in recyclerview?Kornegay
U
7

When the device is rotated, the app is restarted. So onSaveInstance is called before the app gets destroyed. You can save the array adapter in onSaveInstance and when the onCreate is finally called when the app is started again, you can retrieve the array adapter and set it to the list view.

Urbanist answered 22/5, 2013 at 13:53 Comment(3)
Yes, only the foreground activity is recreated but along with it the fragments inside the activity is recreated also.Urbanist
It doesn't make sense to "save an ArrayAdaptor". The correct approach is to save the data used by the ArrayAdaptor and then re-create the ArrayAdaptor - see my outline solution.Swagger
That is understood. You don't save an array adapter, you save only the list data whether it is an array or an arraylist or an arraylist of objects, whatever.Urbanist
P
6

It is pretty easy to save the items of your Adapter in onSaveInstance.

Now you need to save the location (Scroll). You can do this

the easy way

Since changing screenOrientation will mess things up anyway, you can allow for some marginal error and simply, in your onSaveInstance, save the first visible item using listView.getFirstVisiblePosition().

Then in your onRestoreInstance you can retrieve this index to scroll to the same item using listview.setSelection(position). Of course, you can use listview.smoothScrollToPosition(position) but that would be weird.


the hard way

To have more exact position, you will need to go down 1 more level: Get the scroll position (not index) and save that position. Then after restoration, scroll back to this position. In your onSaveInstance, save the position returned from listview.getScrollY(). When you retrieve this position in your onRestoreInstance, you can scroll back to this position using listview.scrollTo(0, position).


Why is it really a hard way?

Simple. You will most probably not be able to scroll back to this position because your listview will not have passed the measurement and layout yet. You can overcome this by waiting for the layout after setting the adapter using getViewObserver().addOnGlobalLayoutListener. In this callback, post a runnable to scroll to the position.


I advice to use the first "easy way" since in general when the screen orientation has been changed the user will probably move his fingers back. We just need to show him the same items "give or take".

Persas answered 31/5, 2013 at 13:11 Comment(0)
U
2

when orientation changes the activity lifecycle calls onPause and then onRestart Do something like this -

@Override
protected void onRestart() {
    super.onRestart();
    mAdapter.getFilter().filter(getFilterSettings());
    mAdapter.notifyDataSetChanged();
}
Undress answered 26/5, 2013 at 20:13 Comment(4)
you are not right, docs clearly state that activity is recreated upon orientation change.Conics
just create methods onPause() and onRestart() and print log in them and see what happens.Undress
done that. onRestart is not called for normal activity w/o configChanges parameters.Conics
Hook into onSaveInstanceState instead. This is exactly what its there for.Swagger
B
0

To save data for the list view as not to reload again and again during the fragment recreation is to save that data to a static member of a class. Something like

class YourData{
public static List<YourDataObject> listViewData;
}

And then from adapter first load the data from internet or from the local database then set that retrieved data to that static member something like this:

 @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       //Run the process to get data from database
      YourData.listViewData = dataRetrievedFromDatabase;
    } 

and then in your onResume method update these values to the adapter something like:

@Override
    public void onResume() {
        super.onResume();
       if(YourData.listViewData!=null){
          yourListAdapater.setData = YourData.listViewData;
          listView.setAdapter(yourListAdapater);
        }else{
      //run your method to get data from server 
         }
    }

this can be used to save any kind of data for a single session

Behan answered 3/2, 2017 at 13:0 Comment(1)
This won't work reliably as Android can reclaim memory when an application or activity is not active. To work with the Android framework and Fragment lifecycle you should implement the onSaveInstanceState method in your Fragment.Swagger
G
-1

I don't know the contents of your ArrayAdapter or the List backing it but what about making the data backing the list serializable, saving it, and then loading it when the view is recreated?

Traditionally I've seen this appraoch used when you're trying to store data when the app is being closed or risks being killed from remaining in the background. There is a great post on serialization complete with sample code here. They walk through taking an ArrayList of custom objects, writing it to a file and reopening it later. I think if you implemented this approach, writing the data of your backing List or ArrayAdapter in onPause() before the activity is destroyed, you could then reload that file when the activtivy is recreated.

Other approaches (a/k/a backup plans):

(1) Easy but sloppy - If your list is some primative, like a list of strings, you could always consider writing the values individually to SharedPreferences and reclaiming them on reload. Just be sure to assign some unique id in the storing process.

NOTE while this may work, SharedPreferecnes is generally not designed to handle large amounts of data so if you're list is long I would avoid this approach. For a few data points, however, I can't see it being a problem.

(2) Slightly harder but risky - If you're List backing the adapter contains objects that implement parcelable or are already serializable, consider passing the List via an Intent to an existing activity that is in the background and using a callback to retrieve that data when the activity is recreated. This is similar to your idea of creating a background Fragment. Both approaches run the risk that the Activity or Fragment you are targeting will not be there.

Gironde answered 29/5, 2013 at 15:57 Comment(2)
Why use SharedPreferences when you have the onSaveInstanceState method ? As for passing the data to another part of the application - messy and ultimately unhelpful if Android destroys the application to recover memory if it is in the background. Understand the Activity and Fragment lifecycle and work with it.Swagger
Yea I don't think its a good idea and certainly savedInstanceState is better. But if you just had to store a couple of strings for a short time, its just a fast way to do it.Gironde

© 2022 - 2024 — McMap. All rights reserved.