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?
when orientation changes the activity lifecycle calls
onPause
and thenonRestart
Do something like this -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 usinglistView.getFirstVisiblePosition()
.Then in your
onRestoreInstance
you can retrieve this index to scroll to the same item usinglistview.setSelection(position)
. Of course, you can uselistview.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 fromlistview.getScrollY()
. When you retrieve this position in youronRestoreInstance
, you can scroll back to this position usinglistview.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".
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):You can then retrieve the data in your onCreate method (or onCreateView or onActivityCreated - see the Fragment JavaDoc) like this:
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.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
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:
and then in your onResume method update these values to the adapter something like:
this can be used to save any kind of data for a single session
When the device is rotated, the app is restarted. So
onSaveInstance
is called before the app gets destroyed. You can save the array adapter inonSaveInstance
and when theonCreate
is finally called when the app is started again, you can retrieve the array adapter and set it to the list view.I don't know the contents of your
ArrayAdapter
or theList
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 backingList
orArrayAdapter
inonPause()
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 implementparcelable
or are already serializable, consider passing theList
via anIntent
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 backgroundFragment
. Both approaches run the risk that theActivity
orFragment
you are targeting will not be there.