Reputation: 1559
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?
Upvotes: 42
Views: 47580
Reputation: 11
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
Upvotes: 0
Reputation: 4591
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.
Upvotes: 56
Reputation: 12201
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();
}
Upvotes: 2
Reputation: 45942
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".
Upvotes: 6
Reputation: 6516
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.
Upvotes: 7
Reputation: 7663
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.
Upvotes: -1