84

I'm migrating my ListViews to RecyclerViews. With listviews I used the common technique described here to store and restore scroll position between activities.

How to do the same with RecyclerViews? the RecyclerView.onSaveInstanceState() seem to have protected access, so can't be used directly.

Community
  • 1
  • 1
khusrav
  • 5,267
  • 5
  • 27
  • 38

9 Answers9

96

Ok, so to answer my own question. As I understand it, since they've decoupled the layout code and the view recycling code (thus the name), the component responsible one for holding layout state (and restoring it) is now the LayoutManager used in your recyclerview.

Thus, to store state you use same pattern, but on the layout manager and not the recyclerview:

protected void onSaveInstanceState(Bundle state) {
     super.onSaveInstanceState(state);

     // Save list state
     mListState = mLayoutManager.onSaveInstanceState();
     state.putParcelable(LIST_STATE_KEY, mListState);
}

Restore state in the onRestoreInstanceState():

protected void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);

    // Retrieve list state and list/item positions
    if(state != null)
        mListState = state.getParcelable(LIST_STATE_KEY);
}

Then update the LayoutManager (I do in onResume()):

@Override
protected void onResume() {
    super.onResume();

    if (mListState != null) {
        mLayoutManager.onRestoreInstanceState(mListState);
    }
}
khusrav
  • 5,267
  • 5
  • 27
  • 38
  • 3
    what if `onRestoreInstanceState()` is never called ? – Someone Somewhere Feb 01 '16 at 00:48
  • Hi, would your code above help me with a RecyclerView list where instancestate is not being saved between activities? I posted the following question here: http://stackoverflow.com/questions/35413495/android-how-do-i-return-to-newly-created-recyclerview-list – AJW Feb 15 '16 at 22:36
  • @AJW the best way to answer would be to run it. – khusrav Feb 18 '16 at 21:53
  • 12
    But what about adapter? – Artem Novikov Sep 22 '16 at 23:41
  • 4
    You can streamline your onRestoreInstanceState() further by eliminating the null check. As per Android documentation: "The system calls onRestoreInstanceState() only if there is a saved state to restore, so you do not need to check whether the Bundle is null". This is not me nitpicking or anything. – Konaras Nov 21 '16 at 08:58
  • 1
    shouldn't you be calling `state.getParcelable` with the parameter `LIST_STATE_KEY` rather than `"myState"`? – Jon McClung Dec 02 '16 at 23:18
  • @JonMcClung yes. Actually in my code they both had this value, so I haven't paid attention when answering. But, it would be correct to use same variables. – khusrav Dec 02 '16 at 23:22
  • 1
    If you're using a `Fragment`, instead of restoring the state on `onRestoreInstanceState`, you can safely do it on `onActivityCreated`. – Mauker Jul 27 '17 at 13:08
  • @khusrav I have recyclerView whose state I want put into savedInstance state but whenever I try to restore its state it gives me error that it is null. I am following same code of yours. – Prashant Abdare Apr 15 '18 at 12:31
  • @khusrav I have a similar issue here: stackoverflow.com/questions/52176812/…. I would appreciate any insights you have on how to solve. – AJW Sep 05 '18 at 03:58
  • @AJW for a similar table, I have a special column called 'position' - it represents the order in which items should be shown. When drag-n-dropping, I change the values of the position of the item dragged and all others affected. – khusrav Sep 05 '18 at 06:54
  • @khusrav Ok I understand. Do you save the positions in a database or an ArrayList or SparseArray using onSaveInstanceState() and then onRestoreInstanceState()? And then retrieve the database position or Array in onResume() or onCreate()? – AJW Sep 06 '18 at 01:24
  • What is "mListState" – MML Jun 29 '22 at 09:28
  • @MML Parceable, as in the cross-linked question – khusrav Jun 30 '22 at 10:16
21

I found a better solution - this solution has the following benefits :

  1. RecyclerView state is saved and restored on rotation of phone
  2. RecyclerView state is saved and restored on returning to activity with RecyclerView (which wasn't destroyed whilst the other activity was showing - which means that onRestoreInstanceState() isn't called !!)

CODE

public class ActivityItemList extends AppCompatActivity
{
    private final String KEY_RECYCLER_STATE = "recycler_state";
    private RecyclerView mRecyclerView;
    private static Bundle mBundleRecyclerViewState;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list);//set to whatever layout name you have

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);//set to whatever view id you use
        // don't forget to set your adapter
    }

    @Override
    protected void onPause()
    {
        super.onPause();

        // save RecyclerView state
        mBundleRecyclerViewState = new Bundle();
        Parcelable listState = mRecyclerView.getLayoutManager().onSaveInstanceState();
        mBundleRecyclerViewState.putParcelable(KEY_RECYCLER_STATE, listState);
    }

    @Override
    protected void onResume()
    {
        super.onResume();

        // restore RecyclerView state
        if (mBundleRecyclerViewState != null) {
            Parcelable listState = mBundleRecyclerViewState.getParcelable(KEY_RECYCLER_STATE);
            mRecyclerView.getLayoutManager().onRestoreInstanceState(listState);
        }
    }
}
Someone Somewhere
  • 23,475
  • 11
  • 118
  • 166
  • Hi, would your code above help me with a RecyclerView list where instancestate is not being saved between activities? I posted the following question here: http://stackoverflow.com/questions/35413495/android-how-do-i-return-to-newly-created-recyclerview-list? – AJW Feb 15 '16 at 19:12
  • I added your code above to my RecyclerView activity with no luck. When going to a previous activity and then returning to the newly created RecyclerView list, the list of new items is lost and I only see the default list layout. – AJW Feb 16 '16 at 01:14
  • 5
    Why do you save the state in `onPause()`? At this time the activity is not destroyed. The state is intact. There's nothing to restore. – WindRider Sep 02 '16 at 15:59
  • out of frustration because I've had moments of OnSaveInstanceState() not being called :/ – Someone Somewhere Sep 12 '16 at 13:14
  • 1
    I was doing some additional research and discovered in Google's sample code that it's probably much better to simply save and restore the scroll position instead of serializing the manager's state. See \sdk\samples\android-21\ui\views\RecyclerView\Application\src\main\java\com\example\android\recyclerview\RecyclerViewFragment.java – Someone Somewhere Jan 02 '17 at 11:43
  • 3
    Using static variables to save state during configuration changes is blasphemy in the Android world. You should definitely use the lifecycle from Android which is designed precisely for this task. – Jelle Fresen Feb 25 '17 at 14:03
  • This will work, but you don't have to use a static reference. You can just store it in the bundle of onSaveInstanceState, and restore it in onCreate. – android developer Oct 29 '17 at 11:25
  • yeah, I do it like that now. However, when I made this post I observed `OnSaveInstanceState()` not being called. – Someone Somewhere Oct 31 '17 at 14:03
  • @Someone Somewhere I have a similar issue here: stackoverflow.com/questions/52176812/…. I would appreciate any insights you have on how to solve. – AJW Sep 05 '18 at 04:01
  • This solution works with ViewModel. if we need to save state of view, located in Fragment, we need to bind ViewModel in activity and save/restore states from variables from ViewModel. And we don't need any static variables =) – zayn1991 Jan 22 '19 at 14:22
15

Use this code in onPause() and onResume() to save and restore scroll position-

private Parcelable recyclerViewState;
recyclerViewState = mrecyclerView.getLayoutManager().onSaveInstanceState();//save
mrecyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);//restore
bozzmob
  • 12,364
  • 16
  • 50
  • 73
mahandis mehdi
  • 159
  • 1
  • 4
9

That's my solution, it restores both items and RecyclerView position

1) save recycler view state in onSaveInstanceState method

@Override
protected void onSaveInstanceState(Bundle outState) {
    Parcelable listState = myRecyclerView.getLayoutManager().onSaveInstanceState();
    // putting recyclerview position
    outState.putParcelable(SAVED_RECYCLER_VIEW_STATUS_ID, listState);
    // putting recyclerview items
    outState.putParcelableArrayList(SAVED_RECYCLER_VIEW_DATASET_ID,mDataset);
    super.onSaveInstanceState(outState);
}

2) Check savedInstanceState Bundle in onCreate method

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState==null){
            getRemoteData(); // No saved data, get data from remote
        }else{
            restorePreviousState(); // Restore data found in the Bundle
        }
    }

3) Restore recycler view data if the screen has been rotated

public void restorePreviousState(){
    // getting recyclerview position
    mListState = mSavedInstanceState.getParcelable(SAVED_RECYCLER_VIEW_STATUS_ID);
    // getting recyclerview items
    mDataset = mSavedInstanceState.getParcelableArrayList(SAVED_RECYCLER_VIEW_DATASET_ID);
    // Restoring adapter items
    mAdapter.setItems(mDataset);
    // Restoring recycler view position
    mRvMedia.getLayoutManager().onRestoreInstanceState(mListState);
}
Nicola Gallazzi
  • 7,897
  • 6
  • 45
  • 64
  • 5
    You shouldn't save your dataset with savedInstanceState. https://developer.android.com/topic/libraries/architecture/viewmodel.html#viewmodel_vs_savedinstancestate – jakk Jul 18 '17 at 11:33
  • Thanks for the hint @jakk, I'll give ViewModel class a try! – Nicola Gallazzi Jul 23 '17 at 15:13
  • ViewModel is a bad idea. Be careful if you want to use it. https://www.techyourchance.com/android-viewmodel-architecture-component-harmful/ – Ashok Koyi Jul 25 '17 at 12:26
  • Thanks for your hint @Kalinga. Did you have bad experience with ViewModel? – Nicola Gallazzi Jul 27 '17 at 08:48
  • No. I have decided not to use it, as it adds more code bloat instead of aiding you & does not address save/restore flow, which you have to address in anycase. Once you implement save/restore, there is no real advantage of using ViewModel – Ashok Koyi Jul 27 '17 at 10:14
  • I attended the Udacity Android Nanodegree developer program and during the course the ViewModel was not mentioned despite the "SavedInstanceState" method, so I guess the second one is the right thing to do in this cases – Nicola Gallazzi Jul 28 '17 at 13:41
  • 1
    @Nicola Gallazzi In your #2 above, what is mSavedInstanceState defined as? – AJW Sep 06 '18 at 01:30
  • 1
    @AJW thanks for the question, that was a mistake. I edited the answer replacing mSavedInstanceState with savedInstanceState coming from onCreate method – Nicola Gallazzi Sep 06 '18 at 07:32
  • @Nicola Gallazzi Ok, understood and thank you for the clarification. – AJW Sep 07 '18 at 04:05
  • mAdapter.setItems(mDataset); my adapter doesn't have a setItem method – Acauã Pitta Apr 10 '20 at 13:13
  • @AcauãPitta it's a simple setter of your adapter dataset, like you do in a normal POJO class – Nicola Gallazzi Apr 10 '20 at 13:24
3

I know i am late but still if it helps!!

Storing the recycler view position is lot simpler than what other answers have made it look like

Here's how you can do it

First create a member variable

Parcelable state;

Now,

@Override
protected void onPause() {
    super.onPause();
    state = recyclerView.getLayoutManager().onSaveInstanceState();
}

@Override
protected void onResume() {
    super.onResume();
    recyclerView.getLayoutManager().onRestoreInstanceState(state);
}

overide on pause and on resume methods with the above code and you are good to go!!

Shubham Shah
  • 147
  • 3
  • 6
1

You can either use adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY which is introduced in recyclerview:1.2.0-alpha02

https://medium.com/androiddevelopers/restore-recyclerview-scroll-position-a8fbdc9a9334

but it has some issues such as not working with inner RecyclerView, and some other issues you can check out in medium post's comment section.

Or you can use ViewModel with SavedStateHandle which works for inner RecyclerViews, screen rotation and process death.

Create a ViewModel with saveStateHandle

val scrollState=
    savedStateHandle.getLiveData<Parcelable?>(KEY_LAYOUT_MANAGER_STATE)

use Parcelable scrollState to save and restore state as answered in other posts or by adding a scroll listener to RecyclerView and

recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        super.onScrollStateChanged(recyclerView, newState)
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
           // save here
        }
    }
Thracian
  • 43,021
  • 16
  • 133
  • 222
0

When you use recyclerView.getLayoutManager().onSaveInstanceState() don't forget to check for null:

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);

    if (recyclerView != null) {
        outState.putParcelable(SCROLL_POSITION, recyclerView.getLayoutManager().onSaveInstanceState());
    }
}
CoolMind
  • 26,736
  • 15
  • 188
  • 224
-1
public class MainActivity extends AppCompatActivity {
Parcelable recyclerViewState;
.......
@Override
    protected void onPause() {
        super.onPause();
        recyclerViewState = MainAnecdotesView.getLayoutManager().onSaveInstanceState();//save
    }
    @Override
    protected void onResume()
    {
        super.onResume();
        if(recyclerViewState!=null)
            MainAnecdotesView.getLayoutManager().onRestoreInstanceState(recyclerViewState);//restore
    }
}
Vlad
  • 365
  • 3
  • 6
-2

Considering that you defined a RecyclerView (mRecyclerView) and a LayoutManager (mLayoutManager) in your code and all is working fine so far, the solution on saving the position (mPosition) of your RecyclerView looks like this:

  1. Variables and constants used:

    private final String RECYCLER_POSITION_KEY = "recycler_position";
    private int mPosition = RecyclerView.NO_POSITION;
    private RecyclerView mRecyclerView;
    private LinearLayoutManager mLayoutManager;
    private static Bundle mBundleState;
    
  2. In onPause method:

    @Override
    protected void onPause()
    {
        super.onPause();
    
        // Save RecyclerView state
        mBundleState = new Bundle();
        mPosition = mLayoutManager.findFirstCompletelyVisibleItemPosition();
        mBundleState.putInt(RECYCLER_POSITION_KEY, mPosition);
    }
    
  3. In onResume method:

    @Override
    protected void onResume()
    {
        super.onResume();
    
        // Restore RecyclerView state
        if (mBundleState != null) {
            mPosition = mBundleState.getInt(RECYCLER_POSITION_KEY);
            if (mPosition == RecyclerView.NO_POSITION) mPosition = 0;
            // Scroll the RecyclerView to mPosition
            mRecyclerView.smoothScrollToPosition(mPosition);
        }
    }
    
  4. In onSaveInstanceState method:

    @Override
    public void onSaveInstanceState(Bundle outState) {
        // Save RecyclerView state
        outState.putInt(RECYCLER_POSITION_KEY,  mLayoutManager.findFirstCompletelyVisibleItemPosition());
    
        super.onSaveInstanceState(outState);
    }
    
  5. In onRestoreInstanceState method:

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        // Restore RecyclerView state
        if (savedInstanceState.containsKey(RECYCLER_POSITION_KEY)) {
            mPosition = savedInstanceState.getInt(RECYCLER_POSITION_KEY);
            if (mPosition == RecyclerView.NO_POSITION) mPosition = 0;
            // Scroll the RecyclerView to mPosition
            mRecyclerView.smoothScrollToPosition(mPosition);
        }
    
        super.onRestoreInstanceState(savedInstanceState);
    }
    
  • Don't store your state in static variable, that is bad practice and it will break your code. – lsrom Sep 09 '22 at 07:50