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;
}
}
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.
Related
My app was very laggy, so I decided to use an AsyncTask to do the heaviest operations inside it and so, the app wouldn't be so slow at changing tabs.
But now, it is behaving in a very weird way. Let me explain: I have a ViewPager2, and inside that ViewPager, I have a recyclerview.
I put an AsyncTask inside the ViewPager, because it is the heaviest operation done in the fragment, and in the adapter of that ViewPager, I retrieve some values from a Database via a class called DatabaseHelper which one that extends SQLiteOpenHelper and has this method.
public Cursor getAllTasksByList(int ListID)
{
SQLiteDatabase db = this.getWritableDatabase();
Cursor c = db.rawQuery("SELECT * FROM " + Db.Tables.Tasktable.TASKS_TABLE + " WHERE " + Db.Tables.Tasktable.COL_LIST_ID + " = " + ListID, null);
return c;
}
Because the DatabaseHelper only returns one Cursor, I use another class to keep the code organized, this class takes the Cursor as argument and returns a list of "ListItem". This class is called "FolderUtils" and contains the following method (which one that I use to populate my RecyclerView inside that is inside my ViewPager):
public ArrayList<TaskItem> getTasksByList(int ListID, Context context) {
ArrayList<TaskItem> tasks = new ArrayList<>();
DatabaseHelper d = new DatabaseHelper(context);
Cursor c = d.getAllTasksByList(ListID);
while (c.moveToNext()) {
int id = c.getInt(0);
int listid = c.getInt(1);
boolean checked = c.getInt(2) > 0;
String title = c.getString(3);
tasks.add(new TaskItem(id, listid, checked, title));
}
return tasks;
}
But here it is the problem, sometimes this List is empty, but another times, it just retrieves the first value of the that Table I look for, strangely, sometimes it returns wrong values and it only works sometimes if I move my ViewPager to another position or if I just put some breakpoints. Here is my Adapter code.
#Override
public void onBindViewHolder(#NonNull ListHolder holder, int position) {
new LoadData(mList.get(position), holder).execute();
}
#Override
public int getItemCount() {
return mList.size();
}
private class LoadData extends AsyncTask<Void, Void, Void> {
private ListItem item;
private ListHolder holder;
public LoadData(ListItem item, ListHolder holder) {
this.item = item;
this.holder = holder;
}
#Override
protected void onPreExecute(){
super.onPreExecute();
//I set the visibility to GONE so that the user can just see the final layout and not the layout "Building" itself.
holder.itemView.setVisibility(View.GONE);
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
setItems(item, holder); //setItems is for setting the UI Content.
AttachRecycler(holder, item); //AttachRecycler creates an adapter for the recyclerview with the TaskList values, and attaches it to the recyclerview inside the ViewPager item.
holder.itemView.setVisibility(View.VISIBLE); //Shows the finished item
}
#Override
protected Void doInBackground(Void... voids) {
SetList(item); //SetList is where it takes the values from database and adds it to the list.
return null;
}
}
private void SetList(ListItem item) {
TaskList = new ArrayList<>();
else if (Mode == 1)
{
//Mode by default is 1. The line below does gets executed, however, it returns the wrong values.
TaskList.addAll(FolderUtils.getInstance().getTasksByList(item.getID(), context));
}
private void AttachRecycler(ListHolder holder, ListItem item)
{
LinearLayoutManager manager = new LinearLayoutManager(context);
holder.recycler.setLayoutManager(manager);
adapter = new TaskAdapter(TaskList, item.getColor(), context, item.getID());
holder.recycler.setAdapter(adapter);
}
How could I fix this? Thank You.
Solved this by myself.
Solution was to make TaskList a private variable inside the LoadData class, not a private variable of the entire Adapter, this acts like a local variable for every item instance, removing the duplicates in some items.
I have a Room local database that holds User objects.
These user objects are fetched at my MainActivity onCreare() and are filling a local Arraylist that I hold -
private UserViewModel userViewModel;
private ArrayList<User> usersList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
observeLiveData();
initViewsAndListeners();
}
private void observeLiveData() {
userViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
userViewModel.getAllUsers().observe(this, users -> {
//update a new list of users
usersList = (ArrayList) users;
addCards(-1);
});
}
That arraylist is being iterated inside a loop, creating custom frame layout views as a child to another custom view that holds them all -
private void initViewsAndListeners() {
tinderStackLayout = findViewById(R.id.activity_main_tinder_stack_layout);
mDeleteButton = findViewById(R.id.activity_main_delete_button);
mPassButton = findViewById(R.id.activity_main_pass_button);
mApproveButton = findViewById(R.id.activity_main_approve_button);
mDeleteButton.setOnClickListener(this);
mApproveButton.setOnClickListener(this);
mPassButton.setOnClickListener(this);
listener = new OnCardSwipedListener() {
#Override
public void send(Object object) {
}
#Override
public void onNext(Integer integer) {
if (integer == 1) {
addCards(1);
}
}
};
}
private void addCards(int stackSizeToAdd) {
TinderCardView tinderCardView;
for (int i = index; index < i + (STACK_SIZE + stackSizeToAdd); index++) {
if (index >= usersList.size()) {
index = 0;
i = 0;
addCards(-1);
}
tinderCardView = new TinderCardView(this, listener);
tinderCardView.bind(usersList.get(index));
tinderStackLayout.addCard(tinderCardView);
}
}
The views are being displayed one on the top of the other as cards that are swipable left or right, Tinder style
So, my question is the followin -
I have inside my ViewModel a delete function that takes a user and deletes it from my Room database.
What would be the correct user to delete? is it userList.get(0)? and if so, according to my code as I provided, how do I make the arraylist automatically update itself as the database is being observed with LiveData
Here is an image of my app MainActivty for demonstration purpose -
I saw all the questions which is similar to my question ( in this , this , this and this link )
I had myAdapter.notifyDataSetChanged() in my Activity but it doesn't work
I have 3 classes,
DBHelper - For storing and getting Database contents ( NO ISSUES HERE )
SimpleRecyclerAdapter - Adapter for RecyclerList
ThirdActivity
What i did in ThirdActivity :
I have TextBox to get data and store it in Database and a Button. In
the Onclicklistener of Button, i specified code to
get text from textbox
add it into table using DBHelper
retrive data as ArrayList from DBHelper
myAdapter.notifyDataSetChanged()
When i click the Button, I got Data in LogCat which i specified inside OnclickListener but it is not reflected to the listview.
Here is my code,
ThirdActivity:
public class ThirdActivity extends AppCompatActivity{
private DrawerLayout mDrawerLayout;
DbHelper dbHelper;
EditText et;
Button addButton;
RecyclerView rv;
ArrayList<String> myNotesList;
SimpleRecycler3Adapter adapter3;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.thirdactivity);
myNotesList = new ArrayList<>();
et=(EditText) findViewById(R.id.et);
addButton=(Button)findViewById(R.id.addButton);
rv = (RecyclerView) findViewById(R.id.dbListrv);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getBaseContext());
rv.setLayoutManager(linearLayoutManager);
rv.setHasFixedSize(true);
adapter3 = new SimpleRecycler3Adapter(myNotesList);
rv.setAdapter(adapter3);
dbHelper = new DbHelper(this, null, null, 1);
addButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("DB", "Constructor");
String note=et.getText().toString();
dbHelper.addNote(note);
printData();
}
});
}
public void printData(){
Log.d("DB","Constructor");
myNotesList=dbHelper.databasetostring();
Log.d("DB","Data came"+myNotesList.get(myNotesList.size()-1));
// adapter3 = new SimpleRecycler3Adapter(myNotesList);
// rv.setAdapter(adapter3);
adapter3.notifyDataSetChanged();
}
}
SimpleRecyclerViewAdapter :
public class SimpleRecycler3Adapter extends RecyclerView.Adapter<SimpleRecycler3Adapter.NotesHolder> {
private ArrayList<String> myNotesList=new ArrayList<String>();
String TAG="ThirdAdapter kbt";
RecyclerView rv;
public SimpleRecycler3Adapter(ArrayList<String> myList) {
Log.d(TAG,"Constructor");
Log.d(TAG,"Not null");
int i = 0;
while (i < myNotesList.size()) {
myNotesList.add(myList.get(i).toString());
}
Log.d(TAG,"finish");
}
#Override
public NotesHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
Log.d(TAG,"On create started");
View view2 = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recyclerlist3_item, viewGroup, false);
Log.d(TAG,"ADAP STAR ONCR second switch 2nd line");
NotesHolder viewHolder2 = new NotesHolder(view2);
Log.d(TAG,"ADAP STAR ONCR second switch 3nd line");
return viewHolder2;
}
#Override
public void onBindViewHolder(NotesHolder notesHolder, int i) {
Log.d(TAG, "ONBIND SECOND i value is " + i);
// notesHolder.thumbnail.setImageResource(R.drawable.placeholder);
notesHolder.dblistitem.setText(myNotesList.get(i));
Log.d(TAG,"ONBIND second title issssss");
}
#Override
public int getItemCount() {
return myNotesList.size();
}
class NotesHolder extends RecyclerView.ViewHolder {
protected ImageView thumbnail;
protected TextView dblistitem;
public NotesHolder(View itemView) {
super(itemView);
Log.d(TAG, "JSON Inside HOLDER");
rv=(RecyclerView)itemView.findViewById(R.id.dbListrv);
// thumbnail = (ImageView) itemView.findViewById(R.id.thumbnail);
dblistitem = (TextView) itemView.findViewById(R.id.dblistitem);
}
}
}
You're not updating the myNotesList that is in adapter class but in activity class. But the adapter uses it's local myNotesList.
So on button click, update myNotesList of adapter with latest data available and notify the adapter.
EDIT
Pass the latest data to adapter. Have this method in adapter class and call this before notifyDataSetChanged();
public void updateNotes(ArrayList<String> notesList) {
myNotesList = notesList;
}
1.you are intializing your dbhelper after setting adapter to listview so it couldn't contain any data initially
2.for updating recycler view data list do as follows
myNotesList.clear();
myNotesList.addAll(dbHelper.databasetostring());
adapter3.notifyDataSetChanged();
You have a problem in your SimpleRecyclerViewAdapter, just change this:
while (i < myNotesList.size()) {
myNotesList.add(myList.get(i).toString());
}
For this:
myNotesList = myList;
And in your activity's printData() change:
myNotesList=dbHelper.databasetostring();
for this:
myNotesList.clear();
myNotesList.addAll(dbHelper.databasetostring());
adapter3.notifyDataSetChanged();
Explanation:
First you initialize myNotesList variable:
myNotesList = new ArrayList<>();
Then you initialize adapter3
adapter3 = new SimpleRecycler3Adapter(myNotesList);
But your adapter is not saving the reference, instead you're copying its data into another variable:
while (i < myNotesList.size()) {
myNotesList.add(myList.get(i).toString());
}
Doing that, if you change myNotesList variable in your activity will not modify your adapter's dataset.
In your method printData() you change myNotesList variable. Which will not touch your adapter or its data
public void printData(){
Log.d("DB","Constructor");
myNotesList=dbHelper.databasetostring();
Log.d("DB","Data came"+myNotesList.get(myNotesList.size()-1));
// adapter3 = new SimpleRecycler3Adapter(myNotesList);
// rv.setAdapter(adapter3);
adapter3.notifyDataSetChanged();
}
You can't change myNotesList by changing myList.
public SimpleRecycler3Adapter(ArrayList<String> myList) {
Log.d(TAG,"Constructor");
Log.d(TAG,"Not null");
// int i = 0;
// while (i < myNotesList.size()) {
// myNotesList.add(myList.get(i).toString());
// }
this.myNotesList = myList;
Log.d(TAG,"finish");
}
Not a good idea to call notifyDataSetChanged() when you know exactly what changed in your data collection.
See this implementation here.
They have even documented to use notifyDataSetChanged() as a last resort in this doc.
You get nice animations for free if you use methods like notifyItemInserted() and the rest.
Also do not go on replacing the collection object entirely, see the implmentation link that has been attached.
I'm new to android.
I'm trying to create a recyclerAdapter in my app with loaderManager to load cursor from sqlite asynchronously.
When there is new data available from sqlite database, I want to insert new items on top of recyclerView. But at the same time, also want to maintain current viewing item.
Let's say, I'm currently viewing item range 5th to 10th, when new 10 items come in, current viewing position also should be 15th to 20th (not to new 5th to 10th).
Otherwise, if lots of items (50 to 60 items) insert, user will lose what he was previously looking at.
So, Is there any way that can maintain current viewing item with recyclerView and loaderManager? How Can it be done simply?
Edit
My code are still simple. Nothing complicated yet to mention specially. But if u insist, Here is my codes.
MyActivity.java
public class MyActivity extends BaseActivity
implements LoaderManager.LoaderCallbacks<Cursor>{
private CustomAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_layout);
mAdapter = new CustomAdapter();
RecyclerView recycler = (RecyclerView) findViewById(R.id.recycler_view);
recycler.setAdapter(mAdapter);
recycler.getItemAnimator().setAddDuration(1000);
recycler.getItemAnimator().setChangeDuration(1000);
recycler.setHasFixedSize(true);
LinearLayoutManager llm = new LinearLayoutManager(this);
llm.setOrientation(LinearLayoutManager.VERTICAL);
recycler.setLayoutManager(llm);
getLoaderManager().initLoader(0, null, this);
}
#Override
protected void onDestroy() {
if(mAdapter != null || mAdapter.getItemCount() != 0){
mAdapter.closeCursor();
}
super.onDestroy();
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(this,buildDataUri(),null,null,null,null);
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
mAdapter.swapCursor(cursor);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
CustomAdapter.java
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
Cursor cursor;
public void swapCursor(Cursor c){
cursor = c;
if(c != null){
cursor.moveToFirst();
...
}
notifyDataSetChanged();
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
CardView c = (CardView) LayoutInflater.from(parent.getContext()).inflate(R.layout.card_cardView, parent, false);
return new ViewHolder(c);
}
#Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
...
}
#Override
public int getItemCount() {
return cursor != null ? cursor.getCount() : 0;
}
public void closeCursor() {
cursor.close();
}
public static class ViewHolder extends RecyclerView.ViewHolder{
...
}
#Zoedia I think its how it works, when new items comes in obviously your current items will scroll down.
You can try a work around by not displaying new items in Recycler View as it comes while giving a Floating Button from top and asking user to click on button or Swipe-to-Refresh to load new items. This will help you read the stuff you are currently reading and new items will be displayed (Recycler View will be inflated with new Items) as soon as user click on that button.
Its similar to Facebook Android App implementation (New Stories Button) :
Edit 1:
To add an item at a particular position, you have to add items from your loader at the 0th/initial position as you want the new feed to be at top.
After that call the mAdapter.notifyDataSetChanged(); to reflect that changes in Recycler view with new elements.
If you were using the ArrayList<>, then you just have to call .add(listitem,position) method to add a single item or .addAll(list,position) to add all the ArrayList.
and then call mAdapter.notifyDataSetChanged();.
I have a SQLite database in my app for which I made a ContentProvider class.
I also have a RecyclerView into which I load an ArrayList of objects into its adapter to populate the RecyclerView.
Currently, when the activity starts I get a Cursor via my ContentProvider, loop through the Cursor to create an ArrayList of objects that I then set as part of my RecyclerView.Adapter.
All that works, but what I really want is for my RecyclerView to dynamically update as new data is loaded into the SQLite database via the content provider.
I have seen posts listing this library CursorRecyclerAdapter but I do not want to use it because you do not get the nice RecyclerView animations on insert/delete.
I was trying to somehow use the LoaderManager.LoaderCallbacks<Cursor> call back methods to get a cursor, convert to arraylist, then swap that in my RecyclerView adapter but couldn't figure it out.
Could someone please show me some example code on how to set it up in my Activity so that the RecyclerView will refresh when new data is written into the local database via a local content provider?
Here is what my RecyclerView.Adapter looks like:
public class MyAdapter extends RecyclerView.Adapter<AdapterTodoList.Holder> {
private List<TodoItem> itemList;
private Context mContext;
//data
String message;
Long datetime;
//this class takes a context and a list of the items you want to populate into the recycler view
public AdapterTodoList(Context context, List<TodoItem> itemList) {
this.itemList = itemList;
this.mContext = context;
}
#Override
public Holder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
//our xml showing how one row looks
View row = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_todo_item, viewGroup, false);
Holder holder = new Holder(row);
return holder;
}
#Override
public void onBindViewHolder(Holder holder, final int position) {
holder.recyclerLinearLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(mContext, "Recycle Click" + position, Toast.LENGTH_SHORT).show();
}
});
//get one item
TodoItem data = itemList.get(position);
Log.d("Test", "onBindViewHolder position " + position);
message = data.getMessage();
datetime = data.getDatetime();
//convert long to date
String dateString = new SimpleDateFormat("MM/dd/yyyy").format(new Date(datetime));
//set the holder
holder.messageTextView.setText(message);
}
#Override
public int getItemCount() {
return itemList.size();
}
public class Holder extends RecyclerView.ViewHolder {
protected ImageView checkBoxImageView;
protected TextView messageTextView;
protected LinearLayout recyclerLinearLayout;
public Holder(View view) {
super(view);
//checkBoxImageView = (ImageView) view.findViewById(R.id.checkBoxImageView);
messageTextView = (TextView) view.findViewById(R.id.messageTextView);
//the whole view
recyclerLinearLayout = (LinearLayout) view.findViewById(R.id.recyclerItemLinearLayout);
}
}
}
Here is what my Activity looks like so far:
public class HomeRec extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>{
private Toolbar mToolbar;
//recyclerview and adapter
private RecyclerView mRecyclerView;
private MyAdapter adapter;
//the swipe refresh layout that wraps the recyclerview
private SwipeRefreshLayout mSwipeRefreshLayout;
//this will hold all of our results from our query.
List<TodoItem> itemList = new ArrayList<TodoItem>();
private Cursor mCursor;
//resources from layout
EditText toDoEditText;
Button cancelButton;
Button addButton;
//variables
private String message;
private long datetime;
//loader
private SimpleCursorAdapter mTodoAdapter;
private static final int TODO_LOADER = 0;
// These indices are tied to Projection. If Projection changes, these
// must change.
public static final int COL_ID = 0;
public static final int COL_MESSAGE = 1;
public static final int COL_DATETIME = 2;
public static final int COL_CHECKED = 3;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home_rec);
mToolbar = (Toolbar) findViewById(R.id.app_bar);
//set the Toolbar as ActionBar
setSupportActionBar(mToolbar);
// Initialize recycler view //
mRecyclerView = (RecyclerView) findViewById(R.id.todoRecyclerView);
mRecyclerView.hasFixedSize();
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
//set a grey line divider for each item in recycler view
mRecyclerView.addItemDecoration(
new DividerItemDecoration(this, null, false, true));
// END Initialize recycler view //
//initiate the swipe to refresh layout
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
// Refresh items
refreshItems();
}
void refreshItems() {
// Load items
// ...
// Load complete
onItemsLoadComplete();
}
void onItemsLoadComplete() {
// Update the adapter and notify data set changed
// ...
// Stop refresh animation
mSwipeRefreshLayout.setRefreshing(false);
}
});
//set colors for swipe to refresh
mSwipeRefreshLayout.setColorSchemeResources(
R.color.refresh_progress_2,
R.color.refresh_progress_3);
//fire my asynctask to get data for the first time
new MessagesAsyncTask().execute();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_home_rec, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
//Not sure what to do here or how to make this work.
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
//Not sure what to do here or how to make this work.
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
//Not sure what to do here or how to make this work.
}
public class MessagesAsyncTask extends AsyncTask<Void, Void, List<TodoItem>> {
//the cursor for the query to content provider
private Cursor mCursor;
#Override
protected void onPreExecute() {
}
#Override
protected List<TodoItem> doInBackground(Void... params) {
// A "projection" defines the columns that will be returned for each row
String[] projection =
{
DataProvider.COL_ID, // Contract class constant for the COL_ID column name
DataProvider.COL_MESSAGE, // Contract class constant for the COL_MESSAGE column name
DataProvider.COL_DATETIME, // Contract class constant for the COL_DATETIME column name
DataProvider.COL_CHECKED // Contract class constant for the COL_CHECKED column name
};
// Defines a string to contain the selection clause
String selectionClause = null;
// An array to contain selection arguments
String[] selectionArgs = null;
// An ORDER BY clause, or null to get results in the default sort order
String sortOrder = DataProvider.COL_DATETIME + " DESC";
// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
DataProvider.CONTENT_URI_TODO, // The content URI of the Todo table
projection, // The columns to return for each row
selectionClause, // Either null, or the word the user entered
selectionArgs, // Either empty, or the string the user entered
sortOrder); // The sort order for the returned rows
// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
// Insert code here to handle the error.
} else if (mCursor.getCount() < 1) {
// If the Cursor is empty, the provider found no matches
} else {
// Insert code here to do something with the results
}
//convert cursor to arraylist of objects
while (mCursor.moveToNext()) {
itemList.add(new TodoItem(mCursor.getInt(mCursor.getColumnIndex(DataProvider.COL_ID)),
mCursor.getString(mCursor.getColumnIndex(DataProvider.COL_MESSAGE)),
mCursor.getLong(mCursor.getColumnIndex(DataProvider.COL_DATETIME)),
mCursor.getInt(mCursor.getColumnIndex(DataProvider.COL_CHECKED))
));
}
mCursor.close();
return itemList;
}
#Override
protected void onPostExecute(List<TodoItem> itemList) {
if (!itemList.isEmpty()) {
adapter = new MyAdapter(HomeRec.this, itemList);
mRecyclerView.setAdapter(adapter);
} else {
Toast.makeText(getApplicationContext(), "No data to display", Toast.LENGTH_LONG).show();
}
}
}
}
I m not sure what you need but I think you should add this method To adapter and call once your data was pulled
public void swapItems(List< TodoItem > todolist){
this.mTodoList = todolist;
notifyDataSetChanged();
}
Hope this would help :D
from your question I assume that you are loading the data from the database and somewhere there is a code that is updating the database. And on every update you want to update your RecyclerView, If this is the case continue reading. I am not going to explain this completely but there are a lot of source that will explain you this.
Use BroadcastReciever : In the place where you are updating your database sendBroadcast(). And in the activity use the BroadcastReceiver
example and in the onReceive() function call load the data in your ArrayList and call the adapter.notifyDataSetChanged()
Instead of making new adapter each time in onPostExecute and set it to recyclerview again you can notify adapter after modifying list elements.
OR
If you want to make adapter using arraylist instead of cursoradapter using loader i have made sample for you with data provided by you. You can use this as a reference:
public class DataBaseActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
private List itemList;
private MyAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_data_base);
RecyclerView recycle=(RecyclerView)findViewById(R.id.rv_data);
SwipeRefreshLayout swipeRefreshLayout= (SwipeRefreshLayout) findViewById(R.id.srl_data);
recycle.setLayoutManager(new LinearLayoutManager(this));
itemList=new ArrayList();
mAdapter= new MyAdapter(this, itemList);
recycle.setAdapter(mAdapter);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
getContentResolver().notifyChange(DataProvider.CONTENT_URI_TODO, null); //if you are using content provider
//getSupportLoaderManager().restartLoader(100, null, DataBaseActivity.this); // if you are using support lib
//getLoaderManager().restartLoader(100, null, DataBaseActivity.this); //if you are not using support lib
}
});
// getLoaderManager().initLoader(100, null, this); //if you are not using support lib
getSupportLoaderManager().initLoader(100, null, this);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] projection =
{
DataProvider.COL_ID, // Contract class constant for the COL_ID column name
DataProvider.COL_MESSAGE, // Contract class constant for the COL_MESSAGE column name
DataProvider.COL_DATETIME, // Contract class constant for the COL_DATETIME column name
DataProvider.COL_CHECKED // Contract class constant for the COL_CHECKED column name
};
// Defines a string to contain the selection clause
String selectionClause = null;
// An array to contain selection arguments
String[] selectionArgs = null;
// An ORDER BY clause, or null to get results in the default sort order
String sortOrder = DataProvider.COL_DATETIME + " DESC";
return new CursorLoader(this,DataProvider.CONTENT_URI_TODO, // The content URI of the Todo table
projection, // The columns to return for each row
selectionClause, // Either null, or the word the user entered
selectionArgs, // Either empty, or the string the user entered
sortOrder);
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if(data!=null && data.getCount()>0)
{
itemList.clear();
while (data.moveToNext()) {
itemList.add(new TodoItem(data.getInt(data.getColumnIndex(DataProvider.COL_ID)),
data.getString(data.getColumnIndex(DataProvider.COL_MESSAGE)),
data.getLong(data.getColumnIndex(DataProvider.COL_DATETIME)),
data.getInt(data.getColumnIndex(DataProvider.COL_CHECKED))
));
}
}
else
Toast.makeText(getApplicationContext(), "No data to display", Toast.LENGTH_LONG).show();
if(data!=null)
data.close();
mAdapter.notifyDataSetChanged();
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
}
}
For "listening" to your ContentProvider changes, you'll could try to integrate ContentObserver into your ContentProvider, so it can trigger the necessary events when a transaction is done on your ContentProvider. After which, you'll declare an ContentObserver to your CONTENT_URI, then you can trigger an update to your RecyclerView.
More info on implementing ContentObserver here.
A sample code for updating an item in your RecyclerView would be,
public void update(T data){
synchronized (mLock){
if(data != null && mData.contains(data)){
int index = mData.indexOf(data);
mData.set(index, data);
notifyItemChanged(index);
}
}
}
Wherein T is the type of object if your row returns, mLock is just an instance object to acquire a lock, mData the list of items you've provided to your RecyclerView. You get the gist. :D
Hope it helps.
Refresh cursor every second
final Handler handler = new Handler();
final int delay = 1000; //milliseconds
handler.postDelayed(new Runnable(){
public void run(){
//Call cursor loader to refresh cursor
getSupportLoaderManager().restartLoader(LOADER_ID, null, MainActivity.this);
handler.postDelayed(this, delay);
}
}, delay);