Android: restore RecyclerView item positions after app re-opens
Asked Answered
S

1

1

I have a RecyclerView list of CardView items. I save CardView data to a SQLite database. The user can drag CardViews up and down in the list to change the order of the items. When the user exits the app, I'd like to save the current order of the RecyclerView items. Then when the user re-opens the app, I'd like to restore that exact order of the RecyclerView items.

I have tried multiple approaches based on other SO posts with no luck:

--How to save RecyclerView's scroll position using RecyclerView.State?

--RecyclerView store / restore state between activities

--How to save scroll position of RecyclerView in Android?

What I get each time I re-open the app is my default order based on the CardView's original timestamp. It shows the newest CardView item at the top of the list, descending to the last item which is the oldest CardView.

Here is my code:

public class MainActivity extends AppCompatActivity {

    private ArrayList<ListItem> allList;
    private RecyclerView mRecyclerView;
    private SQLiteDB sqLiteDB;
    private MyRecylerAdapter adapter;
    private static final String KEY_RECYCLER_STATE = "recycler_state";
    private Parcelable recyclerViewState;

    protected void onCreate(Bundle savedInstanceState) {

        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        allList = new ArrayList<>();
        allList.clear();
        allList = sqLiteDB.getAllDBItems();
        adapter = new MyRecylerAdapter(this, allList);
        mRecyclerView.setAdapter(adapter);
    }

    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState); 

        outState.putParcelable(KEY_RECYCLER_STATE, mRecyclerView.getLayoutManager().onSaveInstanceState());     
    }

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

        recyclerViewState = savedInstanceState.getParcelable(KEY_RECYCLER_STATE);
    }    

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

        if (mRecyclerView !=null) {
            mRecyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);
        }
    }
}    

public class SQLiteDB extends SQLiteOpenHelper {

    ...
    public ArrayList<ListItem> getAllDBItems() {
    ArrayList<ListItem> modelList = new ArrayList<>();

    SQLiteDatabase db = getReadableDatabase();        

    String[] columns = {
            ItemContract.ItemEntry.A,
            ItemContract.ItemEntry.B,
            ItemContract.ItemEntry.C,
            ItemContract.ItemEntry.D,
            ItemContract.ItemEntry.E,
            ItemContract.ItemEntry.F,
            ItemContract.ItemEntry.G,
            ItemContract.ItemEntry.H,
            ItemContract.ItemEntry.I,
            ItemContract.ItemEntry.J
    };

    Cursor getCursor = db.query(
            TABLE_NAME, 
            columns, 
            null,    
            null,   
            null,   
            null,   
            null    
    );

    try {
        if (getCursor.getCount() > 0) {
            getCursor.moveToFirst(); 

            while (!getCursor.isAfterLast()) {

                do { 
                    ListItem listItem = new ListItem();
                    listItem.setId(Integer.parseInt(getCursor.getString(getCursor.getColumnIndex(A))));
                    listItem.setType(getCursor.getString(getCursor.getColumnIndex(B)));
                    listItem.setTypeColor(Integer.parseInt(getCursor.getString(getCursor.getColumnIndex(C))));
                    listItem.setTodo(getCursor.getString(getCursor.getColumnIndex(D)));
                    listItem.setNote1(getCursor.getString(getCursor.getColumnIndex(E)));
                    listItem.setNote2(getCursor.getString(getCursor.getColumnIndex(F)));
                    listItem.setDuedate(getCursor.getString(getCursor.getColumnIndex(G)));
                    listItem.setDuetime(getCursor.getString(getCursor.getColumnIndex(H))); listItem.setTimestamp(Long.parseLong(getCursor.getString(getCursor.getColumnIndex(I))));
                    listItem.setRandint(Integer.parseInt(getCursor.getString(getCursor.getColumnIndex(J))));
                  modelList.add(0,listItem);
                } while (getCursor.moveToNext());
            }
        }
    } finally {
        if (getCursor != null && !getCursor.isClosed()) {
            getCursor.close();
        }
    } if(db.isOpen()) {
        db.close();
    }
    return modelList;
} 

public class ListItem {

    private int _id;
    private int _sortorder;

    public void setSortorder(int sortorder) {
    _sortorder = sortorder;

    } 
}
Sard answered 5/9, 2018 at 3:54 Comment(3)
the order is your state, so let the adapter (which knows the dataset positions) to persist it upon exiting the app, and then let it retrieve it on start up ... use shared prefs to store the order, for instance by item idsSnorter
so use shared prefs to store the adapter positions for the items. then how do I retreive on start-up without running all of the onCreate() that will load up a new RecyclerView list?Sard
In your activity onResume(), you will call myAdapter.refreshDataset(), and there you would do something like mDataset = retrieveDataset(); notifyDataSetChanged(); ... note that your retrieveDataset() could apply different strategies, for instance execute something only when order is different ... the order state, I would represent it with an array of ids, like '[11, 22, 33, 44]' or '[11, 44, 22, 33]'. Note: once you retrieve a newly ordered dataset as Item[] to the adapter, and then you notify, the adapter will do all the rest of the UI heavy work.Snorter
L
1

Add a field called "SortOrder" to your database table and increment the value in this column every time you add a new row. (You could use the MAX function to ensure the value is always the next one up from the current highest)

Sort by this column when retrieving the items.

When you drop a card after dragging it to a new position, update the field with the new sort order/position.

Lasonde answered 5/9, 2018 at 5:23 Comment(10)
I like it. Do I grab the position to save in the "SortOrder" filed from the Adapter? with getAdapterPosition?Sard
Depends on how you are implementing your drag\drop. You must know the position that you are dropping to? Don't forget to update the other items too as their positions could also change depending on where you are dragging\dropping to\from. A library such as this one gives you an "OnItemDragFinished" callback that you can use. github.com/h6ah4i/android-advancedrecyclerviewLasonde
I am using onMove() with an ItemTouchHelper.SimpleCallback object so the RecyclerView's Adapter positions get updated. If I am thinking about this correctly, then I would also update the SortOrder field in the database which can then be used when retrieving the items when the app is re-opened. Sound like I am on the right track?Sard
Sounds like it.Lasonde
So I can add a new column for the database for the SortOrder field. I am struggling with how best to save the Adapter positions to the database. My thought is that I will have to save when the user exits the MainActivity because they are moving to another Activity or they exit the app. I will save the item positions using viewholder.getAdapterPosition(). So do I save them in an ArrayList and then have a Cursor go through the ArrayList to add/update the positions in the SortOrder column? Or add the positions one at a time using Cursor?Sard
It is up to you how you do it. Personally I would update the field as soon as the drag operation finishes but your methods seem very different to what I would do. I have put together a quick sample project here which shows roughly how I would handle it myself: github.com/kuffs/Recyclerview-Drag-DemoLasonde
Ok, I appreciate the feedback and the sample project. I will review.Sard
Ok the drag-and-drop operation is working fine, using the new SortOrder column. But how do I create a value that increments by +1 every time a new RecyclerView item is created, especially for the first item? I even tried to create the item and then use an SQLite UPDATE statement after creation but with no luck. I have added a portion of the ListItem class in the code above, to show the setter for the setSortOrder(). Answer upvoted and accepted.Sard
If you are always adding an item to the end of the list then just use the count of items.Lasonde
The item is always added to the top of the list.Sard

© 2022 - 2024 — McMap. All rights reserved.