Weird behaviour trying to retrieve data with SQLite while on AsyncTask - android

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.

Related

Problem using LoaderManager.LoaderCallbacks<Cursor> with custom RecyclerView.Adapter

I have tried loading the list using the ListView along with LoaderManager.LoaderCallbacks and custom CursorAdapter and it works fine. But I am trying to accomplish the same using RecyclerView along with custom RecyclerView.Adapter but I am getting this issue:
I am getting the list displayed for the first time but when I rotate the device the list disappears.
Here is the code, please have a look.
CatalogActivity
public class CatalogActivity extends AppCompatActivity implements ItemAdapter.OnItemClickListener,
LoaderManager.LoaderCallbacks<Cursor> {
private static final int ITEMS_LOADER_ID = 1;
public static final String EXTRA_ITEM_NAME = "extra_item_name";
public static final String EXTRA_ITEM_STOCK = "extra_item_stock";
#BindView(R.id.list_items)
RecyclerView mListItems;
private ItemAdapter mItemAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_catalog);
ButterKnife.bind(this);
setupListItems();
getLoaderManager().initLoader(ITEMS_LOADER_ID, null, this);
}
private void setupListItems() {
mListItems.setHasFixedSize(true);
LayoutManager layoutManager = new LinearLayoutManager(this);
mListItems.setLayoutManager(layoutManager);
mListItems.setItemAnimator(new DefaultItemAnimator());
mListItems.addItemDecoration(new DividerItemDecoration(this, LinearLayout.VERTICAL));
mItemAdapter = new ItemAdapter(getApplicationContext(), this);
mListItems.setAdapter(mItemAdapter);
}
#Override
public void OnClickItem(int position) {
Intent intent = new Intent(this, EditorActivity.class);
Item item = mItemAdapter.getItems().get(position);
intent.putExtra(EXTRA_ITEM_NAME, item.getName());
intent.putExtra(EXTRA_ITEM_STOCK, item.getStock());
startActivity(intent);
}
private ArrayList<Item> getItems(Cursor cursor) {
ArrayList<Item> items = new ArrayList<>();
if (cursor != null) {
while (cursor.moveToNext()) {
int columnIndexId = cursor.getColumnIndex(ItemEntry._ID);
int columnIndexName = cursor.getColumnIndex(ItemEntry.COLUMN_NAME);
int columnIndexStock = cursor.getColumnIndex(ItemEntry.COLUMN_STOCK);
int id = cursor.getInt(columnIndexId);
String name = cursor.getString(columnIndexName);
int stock = Integer.parseInt(cursor.getString(columnIndexStock));
items.add(new Item(id, name, stock));
}
}
return items;
}
#Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle bundle) {
switch (loaderId) {
case ITEMS_LOADER_ID: {
String[] projection = {
ItemEntry._ID,
ItemEntry.COLUMN_NAME,
ItemEntry.COLUMN_STOCK
};
return new CursorLoader(
this,
ItemEntry.CONTENT_URI,
projection,
null,
null,
null
);
}
default:
return null;
}
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
mItemAdapter.setItems(getItems(cursor));
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
}
}
ItemAdapter
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ItemViewHolder> {
private ArrayList<Item> mItems;
private OnItemClickListener mOnItemClickListener;
private Context mContext;
public ItemAdapter(Context context, OnItemClickListener onItemClickListener) {
mOnItemClickListener = onItemClickListener;
mContext = context;
}
public void setItems(ArrayList<Item> items) {
if (items != null) {
mItems = items;
notifyDataSetChanged();
}
}
public ArrayList<Item> getItems() {
return mItems;
}
public interface OnItemClickListener {
void OnClickItem(int position);
}
public class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
#BindView(R.id.tv_item)
TextView tv_item;
#BindView(R.id.tv_stock)
TextView tv_stock;
public ItemViewHolder(#NonNull View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
int position = getAdapterPosition();
mOnItemClickListener.OnClickItem(position);
}
}
#NonNull
#Override
public ItemViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int i) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_inventory, parent, false);
return new ItemViewHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull ItemViewHolder itemViewHolder, int position) {
final Item item = mItems.get(position);
itemViewHolder.tv_item.setText(item.getName());
itemViewHolder.tv_stock.setText(mContext.getString(R.string.display_stock, item.getStock()));
}
#Override
public int getItemCount() {
if (mItems == null) {
return 0;
} else {
return mItems.size();
}
}
}
I am not able to figure out the extact issue. Please help.
Briefly, the issue here is that, after rotation, you're being handed the same Cursor that you had previously looped over before the rotation, but you're not accounting for its current position.
A Cursor tracks and maintains its own position within its set of records, as I'm sure you've gathered from the various move*() methods it contains. When first created, a Cursor's position will be set to right before the first record; i.e., its position will be set to -1.
When you first start your app, the LoaderManager calls onCreateLoader(), where your CursorLoader is instantiated, and then causes it to load and deliver its Cursor, with the Cursor's position at -1. At this point, the while (cursor.moveToNext()) loop works just as expected, since the first moveToNext() call will move it to the first position (index 0), and then to each available position after that, until the end.
Upon rotation, however, the LoaderManager determines that it already has the requested Loader (determined by ID), which itself sees that it already has the appropriate Cursor loaded, so it just immediately delivers that same Cursor object again. (This is a major feature of the Loader framework – it won't reload resources it already has, regardless of configuration changes.) This is the crux of the issue. That Cursor has been left at the last position to which it was moved before the rotation; i.e., at its end. Consequently, the Cursor cannot moveToNext(), so that while loop just never runs at all, after the initial
onLoadFinished(), before rotation.
The simplest fix, with the given setup, would be to manually reposition the Cursor yourself. For example, in getItems(), change the if to moveToFirst() if the Cursor is not null, and change the while to a do-while, so we don't inadvertently skip over the first record. That is:
if (cursor != null && cursor.moveToFirst()) {
do {
int columnIndexId = cursor.getColumnIndex(ItemEntry._ID);
...
} while (cursor.moveToNext());
}
With this, when that same Cursor object is re-delivered, its position is kinda "reset" to position 0. Since that position is directly on the first record, rather than right before it (remember, initially -1), we change to a do-while, so that the first moveToNext() call doesn't skip the first record in the Cursor.
Notes:
I would mention that it is possible to implement a RecyclerView.Adapter to take a Cursor directly, similar to the old CursorAdapter. In this, the Cursor would necessarily be moved in the onBindViewHolder() method to the correct position for each item, and the separate ArrayList would be unnecessary. It'd take a little effort, but translating CursorAdapter to a RecyclerView.Adapter isn't terribly difficult. Alternatively, there are certainly solutions already available. (For example, possibly, this one, though I cannot vouch for it, atm, I often see a trusted fellow user recommend it often.)
I would also mention that the native Loader framework has been deprecated, in favor of the newer ViewModel/LiveData architecture framework in support libraries. However, it appears that the newest androidx library has its own internal, improved Loader framework which is a simple wrapper around said ViewModel/LiveData setup. This seems to be a nice, easy way to utilize the known Loader constructs while still benefiting from the recent architecture refinements.
Instead of LoaderManager.initLoader() call LoaderManager.restartLoader()

Android: restore RecyclerView item positions after app re-opens

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.

Updating Recycler Listview dynamically using Database

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.

Refresh RecyclerView with latest data dynamically

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);

RecyclerView Image background change randomly

I've implemented a RecyclerView with CardView. Each CardView has an ImageView which I want to change its background color depending on the result of a Query (Empty result set -> Grey / Non Empty result set -> Red), this is implemented on the onBindViewHolder of the RecyclerView Adapter.
Here's the Adapter's Code (I've removed most of the code for the sake of clarity):
public class FavDirsAdapter extends RecyclerView.Adapter<FavDirsAdapter.FavDirsViewHolder> {
private LayoutInflater mInflater;
private Cursor mCursor;
private Context mContext;
private int range;
private FragmentManager mFragmentManager;
public FavDirsAdapter(Context context, Cursor cursor, FragmentManager fm) {
mInflater = LayoutInflater.from(context);
mCursor = cursor;
mContext = context;
range = cursor.getCount();
mFragmentManager = fm;
}
#Override
public FavDirsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = mInflater.inflate(R.layout.item_fav_dirs_list, parent, false);
return new FavDirsViewHolder(view);
}
#Override
public void onBindViewHolder(final FavDirsViewHolder viewHolder, final int position) {
if (mCursor.moveToFirst()) {
mCursor.moveToPosition(position);
viewHolder.favDirsItemTextView.setText(mCursor.getString(mCursor.getColumnIndex(
(FilmoContract.FavDirEntry.COLUMN_DIR))));
getDirImage(viewHolder);
setScheduledFilmsColor(viewHolder);
}
}
#Override
public int getItemCount() {
if (mCursor.moveToFirst()) {
return range;
}
return 0;
}
class FavDirsViewHolder extends RecyclerView.ViewHolder{
TextView favDirsItemTextView;
ImageView favDirsItemImageView;
ImageView favDirsItemScheduledFilmsImage;
public FavDirsViewHolder(View itemView) {
super(itemView);
favDirsItemTextView = (TextView) itemView.findViewById(R.id.fav_dirs_item_text_view);
favDirsItemImageView = (ImageView) itemView.findViewById(R.id.fav_dirs_item_image_view);
favDirsItemScheduledFilmsImage =
(ImageView) itemView.findViewById(R.id.fav_dirs_item_scheduled_films_image_button);
}
}
private void setScheduledFilmsColor(FavDirsViewHolder viewHolder) {
new SetScheduledFilmsColor(viewHolder, mContext).execute();
}
private void getDirImage (FavDirsViewHolder viewHolder) {
new FetchFavDirsImage(viewHolder, mContext).execute();
}
}
The adapter receive a Cursor with a Films Director data list stored on a DB. Then assign each row data to a ViewHolder fileds.
Then I make an additional query to determine wheter each Director has movies scheduled soon, and depending on the query result, color the ImageView (favDirsItemScheduledFilmsImage) background.
I tried first to make the query on the onBindViewHolder Adapter's method, but I've found that all the ImageViews, where colored in the same color (Grey). So since there is a DB Query involved, I've tried to create an AsyncTask (SetScheduledFilmsColor) to do all that stuff. Here's the code:
public class SetScheduledFilmsColor extends AsyncTask<Void, Void, Boolean> {
private ImageView mImageButton;
private String mDirName;
private Context mContext;
boolean scheduledFilms;
static final String[] PROGRAM_COLUMNS = {
FilmoContract.FilmEntry._ID,
FilmoContract.FilmEntry.COLUMN_DATE,
FilmoContract.FilmEntry.COLUMN_TIME,
FilmoContract.FilmEntry.COLUMN_CYCLE,
FilmoContract.FilmEntry.COLUMN_TITLE,
};
public SetScheduledFilmsColor(FavDirsAdapter.FavDirsViewHolder viewHolder, Context context) {
mImageButton = viewHolder.favDirsItemScheduledFilmsImage;
//mImageButton = new ImageButton(mContext);
mDirName = viewHolder.favDirsItemTextView.getText().toString();
mContext = context;
}
#Override
protected Boolean doInBackground(Void... params) {
Uri filmoDirector = FilmoContract.FilmEntry.buildProgramUriWithDirector();
Cursor tempCursor = mContext.getContentResolver().query(
filmoDirector,
PROGRAM_COLUMNS,
mDirName,
null,
null
);
scheduledFilms = tempCursor.moveToFirst();
tempCursor.close();
return scheduledFilms;
}
#Override
protected void onPostExecute(Boolean scheduledFilms) {
super.onPostExecute(scheduledFilms);
mImageButton.setImageResource(R.drawable.ic_new_releases_white_24dp);
if (scheduledFilms) {
//mImageButton.getBackground().clearColorFilter();
mImageButton.getBackground().setColorFilter(
mContext.getResources().getColor(R.color.lafilmo_color), PorterDuff.Mode.MULTIPLY
);
} else {
//mImageButton.getBackground().clearColorFilter();
mImageButton.getBackground().setColorFilter(
mContext.getResources().getColor(R.color.dividers), PorterDuff.Mode.MULTIPLY
);
}
}
}
I'm changing the color in the onPostExecute method. However, even doing this asynchronously, it doesn't matter the query result (I'm sure the query and the onPostExecute condition are fine, I have debug that). Even more, each time I reload the RecyclerView Fragment while navigating through my App, the colored ImageViews are different each time.
I don't understand, how RecyclerView can maintain the reference correctly to the other CardView fields (the fileds on the viewHolder, like favDirsItemTextView or favDirsItemImageView) which are assigned on the onBindViewHolder, and not maintain a reference to the colored ImageView (favDirsItemScheduledFilmsImage).
Can anybody shed some light on this? Is there a better way to do this?
Thanks!

Categories

Resources