DiffUtil Callback with Cursor closed to early - android

I try the new DiffUtil to get the differences in a RecyclerView.Adapter. But the old cursor on a reload is closed before the diff can be calculated and I don't know why. This CursorCallback is the Callback base, this Adapter is my base and here is my activity code:
public class RecyclerActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>{
private RecyclerView recyclerView;
private ItemAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setHasFixedSize( true );
recyclerView.setLayoutManager(new LinearLayoutManager(this) );
recyclerView.setAdapter(adapter = new ItemAdapter(this));
recyclerView.setItemAnimator(new ItemAnimator());
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
long id = recyclerView.getAdapter().getItemId( viewHolder.getAdapterPosition() );
viewHolder.itemView.getContext().getContentResolver().delete(ContentUris.withAppendedId(CategoryContract.CONTENT_URI, id), null, null);
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
getSupportLoaderManager().initLoader(0, null, this);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(this, CategoryContract.CONTENT_URI, CategoryContract.PROJECTION, null, null, CategoryContract.COLUMN_ID + " DESC");
}
public void addItem( View button ) {
int count = recyclerView != null ? recyclerView.getChildCount() : 0;
ContentValues v = new ContentValues(1);
v.put(CategoryContract.COLUMN_NAME, "Foo Nr. " + count);
getContentResolver().insert(CategoryContract.CONTENT_URI, v);
}
private Task setter;
#Override
public void onLoadFinished( final Loader<Cursor> loader, final Cursor data) {
if( setter != null) {
setter.cancel(true);
}
setter = new Task( adapter );
AsyncTaskCompat.executeParallel(setter, adapter.getCursor(), data );
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
adapter.changeCursor(null);
}
public static class Task extends AsyncTask<Cursor, Void, Pair<Cursor, DiffUtil.DiffResult>> {
private final CursorRecyclerViewAdapter adapter;
Task(CursorRecyclerViewAdapter adapter) {
this.adapter = adapter;
}
#Override
protected Pair<Cursor, DiffUtil.DiffResult> doInBackground(Cursor... params) {
return Pair.create( params[1], DiffUtil.calculateDiff( new ItemCallback( params[0], params[1]) ) );
}
#Override
protected void onPostExecute(Pair<Cursor, DiffUtil.DiffResult> diffResult) {
if( isCancelled() )
return;
adapter.swapCursor(diffResult.first);
diffResult.second.dispatchUpdatesTo(adapter);
}
}
public static class ItemAdapter extends CursorRecyclerViewAdapter<ItemHolder>
{
ItemAdapter( Context context ) {
super(context, null);
}
#Override
public void onBindViewHolder(ItemHolder viewHolder, Cursor cursor) {
CategoryModel model = CategoryModel.FACTORY.createFromCursor( cursor );
viewHolder.textView.setText( model.getId() + " - " + model.getName() );
}
#Override
public ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ItemHolder(LayoutInflater.from( parent.getContext() ).inflate( R.layout.item, parent, false ));
}
}
public static class ItemHolder extends RecyclerView.ViewHolder {
TextView textView;
ItemHolder(View itemView) {
super(itemView);
textView = ( TextView ) itemView.findViewById(R.id.textView);
}
}
public static class ItemCallback extends CursorCallback<Cursor> {
public ItemCallback(Cursor newCursor, Cursor oldCursor) {
super(newCursor, oldCursor);
}
#Override
public boolean areRowContentsTheSame(Cursor oldCursor, Cursor newCursor) {
CategoryModel oldCategory = CategoryModel.FACTORY.createFromCursor(oldCursor);
CategoryModel newCategory = CategoryModel.FACTORY.createFromCursor(newCursor);
return oldCategory.getName().equals( newCategory.getName() );
}
#Override
public boolean areCursorRowsTheSame(Cursor oldCursor, Cursor newCursor) {
return oldCursor.getLong(0) == newCursor.getLong(0);
}
}
}
Any help is welcome. Maybe the old cursor is closed when a new cursor with same query is returned. The cursor is open at the moment I call getCursor() in onLoadFinished() but closed inside CursorCallback on first usage.

You have encountered an expected behaviour of CursorLoader — they close the old Cursor after another one arrives, whether you are currently using it or not.
The event sequence in your case is like this:
You get the (still open) Cursor and start a diff computation in some background thread X of AsyncTask thread pool
Something somewhere calls ContentResolver.notifyChanged
The CursorLoader is loading a new Cursor in another background thread Y. That new Cursor is posted to onLoadFinished and, probably, already swapped into the list adapter.
CursorLoader closes old Cursor.
Your background thread X does not know about point 2,3 and keeps using old Cursor until it finds out that it is closed by CursorLoader. An Exception is thrown.
In order to keep using the Cursor from background thread, you will have to manually manage your Cursor (without aid of CursorLoader): close it yourself if configuration change or onDestroy happen.
Alternatively, just intercept the exception and treat it as a sign that your background diff computation is being cancelled (it will shortly be performed for another Cursor anyway).

Related

Recyclerview is not updated after deleting from the sqlite database

I am new in android. I am using content provider for my app and want to delete an item from my sqlite database. it deletes the item from the database but the recyclerview does not show that it has been deleted except you go back and open the activity again before the deleted item disappears. I have looked through the code but I dont actually know what the problem is.
MainActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_selecte_problem_drawal);
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setTitleTextColor(Color.parseColor("#FFFFFF"));
mRecyclerView = (RecyclerView) findViewById(R.id.selected_problem_recycle_view); // Instantiate Recyclerview
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(linearLayoutManager);
mRecyclerView.setHasFixedSize(true);
selectedProblemsAdapter = new SelectedProblemsAdapter(this, null);
mRecyclerView.setAdapter(selectedProblemsAdapter);
getLoaderManager().initLoader(SELECTED_PROBLEMS_LOADER_ID, null,new CartLoader());
payForSelectedProblems = (Button)findViewById(R.id.payForSelectedProblems);
payForSelectedProblems.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (total > 0) {
Preferences.setDefaults("total", String.valueOf(total), getApplicationContext());
Intent intent = new Intent(SelectedProblems.this, SecondPayment.class);
startActivity(intent);
}else{
AlertDialog.Builder alert = new AlertDialog.Builder(SelectedProblems.this);
alert.setTitle("Alert");
alert.setMessage("Kindly Select a problem to continue...");
alert.setPositiveButton("OK",null);
alert.show();
}
}
});
}
public class CartLoader implements LoaderManager.LoaderCallbacks<Cursor> {
#Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(
SelectedProblems.this,
TranxavContract.CartEntries.CONTENT_URI,
SELECTED_PROBLEMS,
null,
null,
null
);
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
selectedProblemsAdapter.update(cursor);
total = getSelectedProblemTotal(cursor);
problemSelectedTotal.setText(CurrencyFormatter.currencyFormat(String.valueOf(total)));
}
#Override
public void onLoaderReset(Loader<Cursor> loader){
selectedProblemsAdapter.update(null);
}
public double getSelectedProblemTotal(Cursor cursor){
while (cursor.moveToNext()){
total = total + Double.parseDouble(cursor.getString(SelectedProblems.PRICE));
}
return total;
}
}
Adapter
public SelectedProblemsAdapter(Context context, Cursor cursor) {
this.context = context;
this.cursor = cursor;
}
#NonNull
#Override
public SelectedProblemsViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(context);
View view = layoutInflater.inflate(R.layout.activity_selected_problems_content, null, false);
return new SelectedProblemsViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull SelectedProblemsViewHolder holder, final int position) {
if (this.cursor != null){
this.cursor.moveToPosition(position);
priceIndex = cursor.getColumnIndex(TranxavContract.CartEntries.PRICE);
problemsIndex = cursor.getColumnIndex(TranxavContract.CartEntries.PROBLEMS);
id = cursor.getString(idIndex);
problems = cursor.getString(problemsIndex);
price = cursor.getString(priceIndex);
formatted_price = CurrencyFormatter.currencyFormat(price);
holder.selectedProblemPrice.setText(formatted_price);
holder.selectedProblems.setText(problems);
holder.remove_problem_from_cart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
context.getContentResolver().delete(
TranxavContract.CartEntries.CONTENT_URI,
TranxavContract.CartEntries.PROBLEMS + " = ?",
new String[] { problems }
);
Toast.makeText(context, problems + " Item Removed from Problem Selected" + "with postion " + position, Toast.LENGTH_SHORT).show();
notifyItemRemoved(position);
notifyDataSetChanged();
}
});
}
}
#Override
public int getItemCount() {
return (null != cursor ? cursor.getCount(): 0);
}
public void update(Cursor cursor) {
this.cursor = cursor;
notifyDataSetChanged();
}
class SelectedProblemsViewHolder extends RecyclerView.ViewHolder{
TextView selectedProblems, selectedProblemPrice, selectedProblemTotal;
Button remove_problem_from_cart;
public SelectedProblemsViewHolder(View itemView) {
super(itemView);
selectedProblems = itemView.findViewById(R.id.selected_problems);
selectedProblemPrice = itemView.findViewById(R.id.selected_problem_price);
selectedProblemTotal = itemView.findViewById(R.id.selectedProblemTotal);
remove_problem_from_cart = itemView.findViewById(R.id.remove_problem_from_cart);
}
}
Anybody who can help me.
For every delete, update your adapter like this
selectedProblemsAdapter.notifydatasetchanged();
Assuming all your deleting process is correct and you want to show animation all over your list in RecyclerView.
Instead of using
notifyItemRemoved(position)
you could implement
notifyItemRangeChanged(position, getItemCount());
notifyItemRangeChanged() notifies the RecyclerView Adapter that positions of element in adapter has been changed from position.
Here is a reference for you.
Hope it helps!
May be the problem is the list hasn't been reload after you deleted the record in db. You can try the method getLoaderManager().restartLoader(int id, Bundle args, LoaderCallbacks<D> callback) this should be restart the query and update it from the db. I hope so. Check more at here
After Deleting recall your display method
(If selectedProblemsAdapter.notifydatasetchanged(); this method is not work)

Async Task blocking UI thread

UPDATE
Thanks to #EmanuelSeibold I was able to pinpoint the problem to the update of the recyclerview. The AsyncTask works in the background just fine, and only the adapter update of the recyclerview freezes the UI.
UPDATE2
I found it was indeed my layout setup. I forgot to remove the nestedScrollView around the RecyclerView. That seemed to cause a rendering conflict.
I dug my way through answers here and blog posts, but just don't seem to be able to find a solution.
I am fairly new to Android development and trying to get an idea on multi-threading.
The scenario: I have an app that holds a SQLite database with course data and implemented a search function that queries that database. This blocks the UI-thread for roughly ~3 seconds.
I therefore implemented an AsyncTask to keep the UI responsive, but my UI is still blocked while the search is ongoing.
Thanks in advance!
Here the code:
Search activity
public class Activity_Search extends Activity_Base {
private RecyclerView rv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
setActionBarTitle(R.string.title_search);
findViewById(R.id.searchCourseTitle).setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_ENTER:
startSearch();
break;
default:
return false;
}
return true;
}
});
rv = (RecyclerView) findViewById(R.id.searchRecycler);
Adapter_Search adapter = new Adapter_Search(this, null);
rv.setAdapter(adapter);
rv.setLayoutManager(new LinearLayoutManager(this));
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.search_FAB);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
startSearch();
}
});
}
private void startSearch() {
findViewById(R.id.searchCourseTitle).clearFocus();
if (this.getCurrentFocus() != null) {
InputMethodManager imm =
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), 0);
}
{getting input}
String[] columns = {...};
String selection = {formatting input};
Async_Search search = new Async_Search(this);
search.execute(columns, new String[]{selection});
}
public void onSearchCompleted(Cursor results) {
((Adapter_Search) rv.getAdapter()).changeCursor(results);
}
}
AsyncTask
public class Async_Search extends AsyncTask<String[], Void, Cursor> {
private Activity activity;
public Async_Search (Activity activity) {
this.activity = activity;
}
#Override
protected Cursor doInBackground(String[]... params) {
SQLiteDatabase db = SQL_Database.getInstance(activity).getWritableDatabase();
String[] columns = params[0];
String selection = params[1][0];
return db.query(...)
}
#Override
protected void onPostExecute(Cursor results) {
((Activity_Search) activity).onSearchCompleted(results);
}
}
Recycler adapter
public class Adapter_Search extends RecyclerCursorAdapter<Adapter_Search.ViewHolder>{
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView name, ects, studipCode, termYear, lecturers, fields;
public ViewHolder (View view) {
super(view);
{id lookups}
}
}
public Adapter_Search(Context context, Cursor cursor) {
super(context, cursor);
}
#Override
public ViewHolder onCreateViewHolder (ViewGroup parent, int ViewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.search_list_entry, parent, false);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder (ViewHolder viewHolder, Cursor cursor) {
TextView name, ects, studipCode, termYear, lecturers, fields;
name = viewHolder.name;
ects = viewHolder.ects;
studipCode = viewHolder.studipCode;
termYear = viewHolder.termYear;
lecturers = viewHolder.lecturers;
fields = viewHolder.fields;
String termandyear = cursor.getString(cursor.getColumnIndexOrThrow(SQL_Database.COURSE_COLUMN_TERM)) +
String.format("%.2s",cursor.getString(cursor.getColumnIndexOrThrow(SQL_Database.COURSE_COLUMN_YEAR)));
name.setText(cursor.getString(cursor.getColumnIndexOrThrow(SQL_Database.COURSE_COLUMN_COURSE)));
String credits = cursor.getString(cursor.getColumnIndexOrThrow(SQL_Database.COURSE_COLUMN_ECTS)) + " ECTS";
ects.setText(credits);
{and so on}
}
}
Base adapter class
public abstract class RecyclerCursorAdapter <ViewHolder extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<ViewHolder> {
private Context mContext;
private Cursor mCursor;
private boolean mDataValid;
private int mRowIdColumn;
private DataSetObserver mDataSetObserver;
public RecyclerCursorAdapter(Context context, Cursor cursor) {
mContext = context;
mCursor = cursor;
mDataValid = cursor != null;
mRowIdColumn = mDataValid ? mCursor.getColumnIndex("_id") : -1;
mDataSetObserver = new NotifyingDataSetObserver();
if (mCursor != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
}
public Cursor getCursor() {
return mCursor;
}
#Override
public int getItemCount() {
if (mDataValid && mCursor != null) {
return mCursor.getCount();
}
return 0;
}
#Override
public long getItemId(int position) {
if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
return mCursor.getLong(mRowIdColumn);
}
return 0;
}
#Override
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(true);
}
public abstract void onBindViewHolder(ViewHolder viewHolder, Cursor cursor);
#Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
if (!mDataValid) {
throw new IllegalStateException("this should only be called when the cursor is valid");
}
if (!mCursor.moveToPosition(position)) {
throw new IllegalStateException("couldn't move cursor to position " + position);
}
onBindViewHolder(viewHolder, mCursor);
}
/**
* Change the underlying cursor to a new cursor. If there is an existing cursor it will be
* closed.
*/
public void changeCursor(Cursor cursor) {
Cursor old = swapCursor(cursor);
if (old != null) {
old.close();
}
}
/**
* Swap in a new Cursor, returning the old Cursor. Unlike
* {#link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
* closed.
*/
public Cursor swapCursor(Cursor newCursor) {
if (newCursor == mCursor) {
return null;
}
final Cursor oldCursor = mCursor;
if (oldCursor != null && mDataSetObserver != null) {
oldCursor.unregisterDataSetObserver(mDataSetObserver);
}
mCursor = newCursor;
if (mCursor != null) {
if (mDataSetObserver != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
mRowIdColumn = newCursor.getColumnIndexOrThrow("_id");
mDataValid = true;
notifyDataSetChanged();
} else {
mRowIdColumn = -1;
mDataValid = false;
notifyDataSetChanged();
//There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
}
return oldCursor;
}
private class NotifyingDataSetObserver extends DataSetObserver {
#Override
public void onChanged() {
super.onChanged();
mDataValid = true;
notifyDataSetChanged();
}
#Override
public void onInvalidated() {
super.onInvalidated();
mDataValid = false;
notifyDataSetChanged();
//There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
}
}
}
Maybe try to remove these lines from the Async Search.
private Activity activity;
public Async_Search (Activity activity) {
this.activity = activity;
}
Instead call like this:
Async_Search search = new Async_Search();
search.execute(columns, new String[]{selection});
I don't know if this will work, but my Asynctask doesn't use the activity stuff yours has.

RecyclerView only updates on app startup

newbie to Android here!
I've been learning how to implement SQLite in my app, and to sum it up, I have an Accountant class which has access to the SQLite database. The class pulls up the items from the database and puts them in an ArrayList. This ArrayList is what is used for my adapter for the recyclerView.
Whenever I add a new item in the app, the the item's data is stored in the database and the Accountant class's ArrayListgets updated with this info.
Then, the adapter calls its notifyDataSetChanged() method to update the View. This is where the problem occurs; the RecyclerView DOES display all items, but only upon app startup, NOT when a new item is added.
I've done all I can, it just LOOKS like it's supposed to work, but it doesn't and it's driving me nuts.
Here's the code
ItemAdapter Class
private class ItemAdapter extends RecyclerView.Adapter<ItemHolder> {
private List<Item> mItemList;
public ItemAdapter(List<Item> itemList) {
mItemList = itemList;
}
public ItemHolder onCreateViewHolder(ViewGroup parent, int ViewType) {
View view = getLayoutInflater().inflate(R.layout.list_item_item, parent, false);
return new ItemHolder(view);
}
public void onBindViewHolder(ItemHolder holder, int position) {
Item item = mItemList.get(position);
holder.bindItem(item);
}
public int getItemCount() {
return mItemList.size();
}
}
Accountant Class
public class Accountant {
private static Accountant sAccountant;
private double mTotalMoney;
private Context mContext;
private SQLiteDatabase mDatabase;
private List<Item> mItemList;
public static Accountant get(Context context) {
sAccountant = sAccountant == null ? new Accountant(context) : sAccountant;
return sAccountant;
}
private Accountant(Context context) {
mTotalMoney = 0;
mContext = context.getApplicationContext();
mDatabase = new ItemBaseHelper(mContext).getWritableDatabase();
mItemList = getListFromSQL();
}
private static ContentValues getContentValues(Item i) {
ContentValues values = new ContentValues();
values.put(ItemTable.cols.NAME, i.getName());
values.put(ItemTable.cols.PRICE, i.getPrice());
values.put(ItemTable.cols.COUNT, i.getCount());
return values;
}
public void addItem(Item item) {
ContentValues cv = getContentValues(item);
mDatabase.insert(ItemTable.NAME, null, cv);
mItemList = getListFromSQL();
}
public void removeItem(int i) {
}
public void addMoney(double money, boolean isSet) {
mTotalMoney += isSet ? money - mTotalMoney : money;
}
public String getTotalMoney() {
return MoneyUtils.prep(mTotalMoney);
}
public String getChange() {
double cost = 0;
for (Item item : getItemList())
cost += item.getPrice() * item.getCount();
return MoneyUtils.prep(mTotalMoney - cost);
}
public List<Item> getItemList() {
return mItemList;
}
private List<Item> getListFromSQL() {
List<Item> itemList = new ArrayList<>();
ItemCursorWrapper cursor = queryItems(null, null);
try {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
itemList.add(cursor.getItem());
cursor.moveToNext();
}
} finally {
cursor.close();
}
return itemList;
}
public ItemCursorWrapper queryItems(String whereClause, String[] whereArgs) {
Cursor cursor = mDatabase.query(ItemTable.NAME, null, whereClause, whereArgs, null, null, null);
return new ItemCursorWrapper(cursor);
}
public String individualPriceOf(Item i) {
return MoneyUtils.prep(i.getPrice());
}
public String totalPriceOf(Item i) {
return MoneyUtils.prep(i.getCount() * i.getPrice());
}
public String countOf(Item i) {
return String.valueOf(i.getCount());
}
public void clearList() {
mDatabase.delete(ItemTable.NAME, null, null);
}
}
Item adding logic
public void addItem(Item item) {
mAccountant.addItem(item);
mAdapter.notifyItemInserted(mAccountant.getListFromSQL().size() - 1);
mAdapter.notifyDataSetChanged();
mChangeButton.setText(mAccountant.getChange());
}
Well there is fundamental problem not even related to RecyclerView.
First let's see how to fix your issue then explanation of what's wrong.
change this
private List<Item> mItemList;
to this
private final List<Item> mItemList;
then instead of any assignment like mItemList = getListFromSQL(); write this
mItemList.clear();
mItemList.addAll(getListFromSQL());
Now explanation why your code is not working. The thing is that when you assign your dataSource (i.e. mItemList) to some new value you are changing reference to it (that's a java fundamental thing) so that your RecyclerView doesn't know anything about it and it's own dataSource which you assign only once in constructor remains the same old one which is not changed therefore your notifyDataSetChanged call does nothing.
General advice whenever using RecyclerView or a ListView make sure you define your dataSource as final.
This is happening because you do not add the item into your Adpater's list. Make a method inside your adapter and call this method from your Accountant class.
private class ItemAdapter extends RecyclerView.Adapter<ItemHolder> {
public void addItem(Item item) {
mItemList.add(item); ///Add the item to your arrayList and then notify
notifyItemInserted(mItemList.size());
}
When you add single item in Adapter dont call notifyDataSetChanged() method because it will notify the whole list. Instead only use notifyItemInserted() method.
Another think is make sure when you notify the adapter it must be from UI thread.
When you add your item then just call this adapter addItem() method from your Accountant class.
public void addItem(Item item) { ///This method is from Accountant Class
mAccountant.addItem(item);
mAdapter.addItem(item); // Call the addItem() from Adapter class
mChangeButton.setText(mAccountant.getChange());
}
Hope it will work...

How to run SQLite query asynchronously

I'm trying to get all the contacts from my SQLite database.
Everything is working fine, I just want to make it asynchronous and not run in the main thread, to not influence the UI.
public List<contacts> getAllcontacts() {
List<contacts> contactsl = new LinkedList<contacts>();
String query = "SELECT * FROM contacts WHERE show is not 'NOTSIGNEDUP'"
+" ORDER BY name COLLATE NOCASE;";
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(query, null);
contacts contact = null;
if (cursor.moveToFirst()) {
do {
contact = new contacts();
contact.setName(cursor.getString(1));
contact.setNumero(cursor.getString(3));
contact.setProfil(cursor.getString(2));
contact.setShow(cursor.getString(5));
contact.setBlocked(cursor.getString(4));
contact.setObjectid(cursor.getString(6));
contactsl.add(contact);
} while (cursor.moveToNext());
}
return contactsl;
}
I'm calling this function from my activity :
final sql s = sql.getInstance(getContext());
if (ContactsList != null) {
ContactsList.clear();
ContactsList.addAll(list);
ContactsList.addAll(s.getAllcontacts_());
cAdapter.notifyDataSetChanged();
}
Is there any way to make s.getAllcontacts() runs asyn
I made my Fragment like this :
public class ContactsFragment extends Fragment implements LoaderManager.LoaderCallbacks<List<contacts>> {
private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
private ContactsAdapter cAdapter;
private List<contacts> ContactsList;
public ContactsFragment() {
// Required empty public constructor
}
public void set(List<contacts> list) {
final sql s = sql.getInstance(getContext());
if (ContactsList != null) {
ContactsList.clear();
ContactsList.addAll(list);
ContactsList.addAll(s.getAllcontacts_());
cAdapter.notifyDataSetChanged();
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_blank, container, false);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
View view = getView();
if(view != null) {
mRecyclerView = (RecyclerView) view.findViewById(R.id.contacts_recycler);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(view.getContext());
mRecyclerView.setLayoutManager(mLayoutManager);
final sql s = sql.getInstance(view.getContext());
ContactsList = new ArrayList<contacts>();
cAdapter = new ContactsAdapter(ContactsList, mRecyclerView);
mRecyclerView.setAdapter(cAdapter);
getLoaderManager().initLoader(0, null, this);
}
}
#Override
public android.support.v4.content.Loader<List<contacts>> onCreateLoader(int id, Bundle args) {
return new AppListLoader(this.getContext());
}
#Override
public void onLoadFinished(android.support.v4.content.Loader<List<contacts>> loader, List<contacts> data) {
ContactsList.addAll(data);
cAdapter.notifyDataSetChanged();
}
#Override
public void onLoaderReset(android.support.v4.content.Loader<List<contacts>> loader) {
}
public static class AppListLoader extends AsyncTaskLoader<List<contacts>> {
final sql s = sql.getInstance(getContext());
public AppListLoader(Context context) {
super(context);
}
#Override
public List<contacts> loadInBackground() {
return s.getAllcontacts();
}
}
}
in addition to what #CommonsWare suggests, you could also use give to the AsyncTaskLoader a try. You could define
public static class AppListLoader extends AsyncTaskLoader<List<Contact>> {
and move your querying logic in loadInBackground().
Your Activity/Fragment will make then use of the LoaderManager. It will implement LoaderManager.LoaderCallbacks<List<Contact>> and onCreateLoader will return a new instance of your AsyncTaskLoader. The List<Contact> will be delivered as part of onLoadFinished

Populating a ListView using AsyncTaskLoader

I'm having problems using AsyncTaskLoader. This is my first attempt populating a ListView from a SQLite database using a loader.
Everything seems ok, when I rotate the screen the data is cached and no query is done again. But when I press the home button and launch my app again, the data is loaded again.
Note: Usuario means User, so I'm populating the ListView with a list of users.
public class Main extends SherlockFragmentActivity
implements LoaderManager.LoaderCallbacks<ArrayList<Usuario>> {
UsuarioAdapter adapter;
ListView listView;
Database db;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
listView = (ListView) findViewById(R.id.lista);
db = new Database(this);
adapter = new UsuarioAdapter(this, new ArrayList<Usuario>());
listView.setAdapter(adapter);
getSupportLoaderManager().initLoader(0, null, this);
}
#Override
public Loader<ArrayList<Usuario>> onCreateLoader(int id, Bundle args) {
return new UsuariosLoader(this, db);
}
#Override
public void onLoadFinished(Loader<ArrayList<Usuario>> loader,
ArrayList<Usuario> usuarios) {
//adapter.notifyDataSetChanged();
listView.setAdapter(new UsuarioAdapter(this, usuarios));
// ((BaseAdapter) listView.getAdapter()).notifyDataSetChanged();
}
#Override
public void onLoaderReset(Loader<ArrayList<Usuario>> loader) {
listView.setAdapter(null);
}
}
// THE LOADER
class UsuariosLoader extends AsyncTaskLoader<ArrayList<Usuario>> {
private ArrayList<Usuario> usuarios;
private Database db;
public UsuariosLoader(Context context, Database db) {
super(context);
this.db = db;
}
#Override
protected void onStartLoading() {
if (usuarios != null) {
deliverResult(usuarios); // Use the cache
}
forceLoad();
}
#Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
}
#Override
public ArrayList<Usuario> loadInBackground() {
db.open(); // Query the database
ArrayList<Usuario> usuarios = db.getUsuarios();
db.close();
return usuarios;
}
#Override
public void deliverResult(ArrayList<Usuario> data) {
usuarios = data; // Caching
super.deliverResult(data);
}
#Override
protected void onReset() {
super.onReset();
// Stop the loader if it is currently running
onStopLoading();
// Get rid of our cache if it exists
usuarios = null;
}
#Override
public void onCanceled(ArrayList<Usuario> data) {
// Attempt to cancel the current async load
super.onCanceled(data);
usuarios = null;
}
}
And I think this snippet is not well done. I'm creating a new Adapter instead of updating the data.
#Override
public void onLoadFinished(Loader<ArrayList<Usuario>> loader,
ArrayList<Usuario> usuarios) {
//adapter.notifyDataSetChanged();
listView.setAdapter(new UsuarioAdapter(this, usuarios));
//((BaseAdapter) listView.getAdapter()).notifyDataSetChanged();
}
Why adapter.notifyDataSetChanged() does not work?
So, basically, my app does not crash but all my data is reloaded again every time I restart the app.
Edit: This is my Adapter code:
class UsuarioAdapter extends BaseAdapter {
private ArrayList<Usuario> usuarios;
private LayoutInflater inflater;
public UsuarioAdapter(Context context, ArrayList<Usuario> usuarios) {
this.usuarios = usuarios;
this.inflater = LayoutInflater.from(context);
}
#Override
public int getCount() { return usuarios.size(); }
#Override
public Object getItem(int pos) { return usuarios.get(pos); }
#Override
public long getItemId(int pos) { return pos; }
#Override
public View getView(int pos, View convertView, ViewGroup arg) {
LinearLayout itemView;
if (convertView == null) {
itemView = (LinearLayout) inflater.inflate(R.layout.list_item, null);
} else {
itemView = (LinearLayout) convertView;
}
ImageView avatar = (ImageView) itemView.findViewById(R.id.avatar);
TextView nombre = (TextView) itemView.findViewById(R.id.nombre);
TextView edad = (TextView)itemView.findViewById(R.id.edad);
// Set the image ... TODO
nombre.setText(usuarios.get(pos).getNombre());
edad.setText(String.valueOf(usuarios.get(pos).getEdad()));
return itemView;
}
}
The call to notifyDataSetChanged() won't change the data your adapter is using. You need to update the data the adapter has, then call that method.
NotifyDataSetChanged() will only tell the adapter it needs to create it's views, but it does not change the data. You need to handle that yourself.
In your adapter add:
public void setUsuario(List<Usuario> usuarios) {
this.usuarios = usuarios;
}
Then in onLoadFinished() call the new method, then notifyDataSetChanged().
listView.getAdapter().setUsuario(usuarios);
listView.getAdapter().notifiyDataSetChanged();
I've found the solution. The onStartLoading was the guilty:
#Override
protected void onStartLoading() {
if (usuarios != null) {
deliverResult(usuarios); // Use cache
} else {
forceLoad();
}
}
In my original post forceLoad was always called. It must be in the else branch.

Categories

Resources