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();
}
}
}
}
Update single item Change the "Sheep" item so that it says "I like sheep." String newValue = "I like sheep."; int updateIndex = 3; data. set(updateIndex, newValue); adapter. notifyItemChanged(updateIndex);
What does notifyDataSetChanged() do on recyclerview ? Notify any registered observers that the data set has changed. There are two different classes of data change events, item changes and structural changes. Item changes are when a single item has its data updated but no positional changes have occurred.
Stay organized with collections Save and categorize content based on your preferences. DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list into the second one. It can be used to calculate updates for a RecyclerView Adapter.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With