RecyclerView blocks UI when setting the adapter - android

I have a requirement in which I have a populated database with over 300k rows. I have successfully implemented a CursorAdapter based in this question, with a mix of the two most up voted answers HERE.
I have implemented an AsyncTask for background service to perform the query to the database which is very fast, doesn't take more than 2-3 seconds. My ProgressDialog from the AsyncTask is at times hard to detect.
My problem is, when the task is done and I retrieve the Cursor, when I set the Adapter to the RecyclerView, the process freezes my UI for a few seconds until the data is set. It also happens when I perform a search (new query, same procedure as getting all the rows but with fewer rows), and replace the Cursor to update the data.
Here is some relevant code:
AsyncTask
#Override
protected Void doInBackground(Void... Void) {
if(type==Constants.GET_ZIP_CODES)
cursor = db.getAllZipCodes();
else
cursor = db.searchZipCodes(text);
return null;
}
#Override
protected void onPostExecute(Void Void) {
setAdapter();
mProgressDialog.dismiss();
super.onPostExecute(Void);
}
Methods
private void setAdapter(){
if(myAdapter == null){
myAdapter = new MyAdapter(getActivity(), cursor);
search_rv.setAdapter(myAdapter);
} else
myAdapter.swapCursor(cursor);
}
Since it is a search I don't have much to do here besides notifyDataSetChanged() because all the data changes widely in every search.
Is this normal? Since a RecyclerView only renders the visible views, why does it freeze and takes so long to update since the Cursor is already ready from the AsyncTask?
EDIT
I have changed my Adapter to avoid using CursorAdapter as #cricket_007 pointed out having an Adapter within an Adapter is bad design.
This is my Adapter:
public class SearchListAdapter extends RecyclerView.Adapter<SearchListAdapter.ViewHolder> {
private Context mContext;
private Cursor mCursor;
private boolean mDataValid;
private int mRowIdColumn;
private DataSetObserver mDataSetObserver;
public SearchListAdapter(Context context, Cursor c) {
mContext = context;
mCursor=c;
mDataValid = c != null;
mRowIdColumn = mDataValid ? mCursor.getColumnIndex("_id") : -1;
mDataSetObserver = new NotifyingDataSetObserver();
if (mCursor != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView itemTV;
ViewHolder(View itemView) {
super(itemView);
itemTV = (TextView) itemView.findViewById(R.id.itemTV);
}
}
#Override
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(true);
}
#Override
public int getItemCount() {
if (mDataValid && mCursor != null) {
return mCursor.getCount();
}
return 0;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// Passing the binding operation to cursor loader
mCursor.moveToPosition(position);
String town = mCursor.getString(mCursor.getColumnIndex(Constants.COLUMN_TOWN));
String zipcode = mCursor.getString(mCursor.getColumnIndex(Constants.COLUMN_ZIPCODE));
String zipcode_etx = mCursor.getString(mCursor.getColumnIndex(Constants.COLUMN_ZIPCODE_EXTENSION));
holder.itemTV.setText(zipcode+"-"+zipcode_etx+", "+town);
}
#Override
public SearchListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_search_list_item,parent,false);
// Passing the inflater job to the cursor-adapter
return new SearchListAdapter.ViewHolder(itemView);
}
public void swapCursor(Cursor cursor) {
Cursor old = changeCursor(cursor);
if (old != null) {
old.close();
}
}
private Cursor changeCursor(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();
}
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();
}
}
}

Well, I found out why this was happening and the reason is weird. The problem has nothing to do with the RecyclerView but with the way the data is fetch.
In my AsyncTask, where I fetch the data, I wrote a Log.d to print the Cursor size like so:
#Override
protected Void doInBackground(Void... Void) {
if(type==Constants.GET_ZIP_CODES)
cursor = db.getAllZipCodes();
else
cursor = db.searchZipCodes(text);
Log.d("DATABASE","SIZE "+cursor.getCount());
return null;
}
This made the AsyncTask take longer, the ProgressDialog takes longer to go off. What I understand is that somehow, the database query is performed, the code keeps compiling, but the data is only ready in the Cursor after a while. Once I printed the result just after the query, it didn't go past the line until the cursor was fully loaded.

Actually this is not an answer(Would have put it in comment if i had enough reputation points) just a suggestion/case study i came across while loading data from database to recyclerView. Instead of directly sending the cursor over to adapter i sent it as an arraylist, but thats besides the point.
The place where i got the freeze like you seem to get is when i have to load a text with over 700-800 character into a card. So when i crop the text to less than 600 the freeze vanishes.
So just check if you have any data with large character set, if so try removing that and test it out.
Hope it works for you, suggestion put read more option for large text like whatsapp does!

Related

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.

DiffUtil Callback with Cursor closed to early

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

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...

CursorAdapter not always working on AutoCompleteTextView

I am having an issue implementing CursorAdapter on an AutoCompleteTextView.
ZipCode : _ (<--EditText)
City : ____ (<-- AutoCompleteTextView)
Basically, I want to help the user suggesting available cities for the Zip Code entered.
My issue is that the suggestions are not shown (Cursor does not launch query i guess). What i don't understand is why it's working in some cases and not in other ones. I attach the faulty case below.
My Cursor Adapter :
public class SearchCursorAdapter extends CursorAdapter {
private DataBaseHelper mDbHelper;
private String codePostal;
public SearchCursorAdapter(DataBaseHelper dbHelper, Context context,
String codePostal) {
// Call the CursorAdapter constructor with a null Cursor.
super(context, null, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
mDbHelper = dbHelper;
this.codePostal = codePostal;
}
#Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
if (getFilterQueryProvider() != null) {
return getFilterQueryProvider().runQuery(constraint);
}
Cursor cursor = mDbHelper.getStationCursor(constraint.toString(),
codePostal);
return cursor;
}
#Override
public String convertToString(Cursor cursor) {
return cursor.getString(1); //second column in select
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
((TextView) view).setText(cursor.getString(1)); //second column in select
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.spinner_layout, null);
return view;
}
}
The select method from db adapter :
public Cursor getStationCursor(String args, String arg2) {
StringBuffer sqlQuery = new StringBuffer("");
Cursor result = null;
sqlQuery.append(" SELECT min(_id) as _id, ");
sqlQuery.append(CITIES.CITY);
sqlQuery.append(" FROM ");
sqlQuery.append(CITIES.TABLE_NAME);
sqlQuery.append(" WHERE ");
sqlQuery.append(CITIES.CITY);
sqlQuery.append(" LIKE '");
sqlQuery.append(args);
sqlQuery.append("%' ");
sqlQuery.append("AND ");
sqlQuery.append(CITIES.CODE_POSTAL);
sqlQuery.append(" LIKE '");
sqlQuery.append(arg2);
sqlQuery.append("%' ");
sqlQuery.append(" GROUP BY ");
sqlQuery.append(CITIES.CITY);
sqlQuery.append(" ORDER BY ");
sqlQuery.append(CITIES.CITY);
sqlQuery.append(" LIMIT 10 ");
if (myDataBase != null) {
result = myDataBase.rawQuery(sqlQuery.toString(), null);
}
if (result != null) {
result.moveToFirst();
}
return result;
}
The code in my activity :
EditText etCodPost;
AutoCompleteTextView acCity;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout....);
etCodPost = (EditText) ...;
acCom = (AutoCompleteTextView) ...;
setComAdapter(activity);
etCodPost.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
#Override
public void afterTextChanged(Editable s) {
setComAdapter(activity);
}
});
}
private void setComAdapter(Activity activity) {
SearchCursorAdapter adapt = new SearchCursorAdapter(myDbHelper, activity,
etCodPost.getText().toString());
acCity.setAdapter(adapt);
acCity.setThreshold(3);
}
Thanks for your replies and sorry for the long post. Any hint would be very appreciated.
You don't mention at least one of the cases where the filtering fails so the lines below come more from guessing:
I think you setup the filtering wrong. There is no need to set the adapter each time when the user enters a code, a simpler solution would be to have a field in the activity class, an int(or String from your code) which would be used as part of the query.
#Override
public void afterTextChanged(Editable s) {
mCode = s.toString;
}
Next, filtering at the adapter level could be improved like this:
#Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
if (constraint == null || constraint.toString.equals("")) {
// this is the contract of this method and should be respected
return mDbHelper.getAllCityRecords(); // get all records
}
return mDbHelper.getStationCursor(constraint.toString(),
mCode); // mCode is the field that is updated in the activity class(you should take in consideration that the user could use directly the AutoCompleteTextView, so mCode could be not set at this level)
return cursor;
}
Last, you probably did this, but do make sure that you get what you expect when querying(directly) the database. As the filtering works for some cases it may be something to look after.

Unable to make AlphabetIndexer work

I'm attempting to implement the AlphabetIndexer to help the users scroll through my list, but nothing shows up on the list when I run the app. Could someone please tell me why?
Note: I am not instantiating an AlphabetIndexer in the Adapter's constructor because, at that point, no Cursor is available.
Here is the relevant code:
In the Activity's onCreate() method:
mList = (ListView)findViewById(R.id.mylist);
mList.setOnItemClickListener(this);
mList.setFastScrollEnabled(true);
mAdapter = new MyAdapter(MyActivity.this, R.layout.layout_list_row, null, new String[] {MyColumns.NAME}, new int[] {R.id.itemname});
mList.setAdapter(mAdapter);
mList.setFastScrollEnabled(true);
doQuery();
doQuery() is a method that queries for a Cursor using an AsyncQueryHandler. The AsyncQueryHandler looks like this:
private final class MyQueryHandler extends AsyncQueryHandler {
public MyQueryHandler(Context context) {
super(context.getContentResolver());
}
#Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
if (!isFinishing()) {
if (mAdapter != null) {
mAdapter.changeCursor(cursor);
}
}
else {
cursor.close();
}
}
}
Lastly, my SimpleCursorAdapter. I've taken out the unnecessary parts:
public class MyAdapter extends SimpleCursorAdapter implements View.OnClickListener {
private Cursor mCursor;
AlphabetIndexer alphaIndexer;
public MyAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c, from, to);
}
public int getPositionForSection(int section) {
return alphaIndexer.getPositionForSection(section);
}
public int getSectionForPosition(int position) {
return alphaIndexer.getSectionForPosition(position);
}
public Object[] getSections() {
return alphaIndexer.getSections();
}
public void onClick(View v) {
// ...
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
// ...
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
// ...
}
#Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
if (MyActivity.this.mCursor != null) {
stopManagingCursor(MyActivity.this.mCursor);
MyActivity.this.mCursor.close();
MyActivity.this.mCursor = null;
mCursor = null;
}
MyActivity.this.mCursor = cursor;
startManagingCursor(MyActivity.this.mCursor);
mCursor = cursor;
alphaIndexer = new AlphabetIndexer(mCursor, mCursor.getColumnIndex(MyColumns.NAME), " ABCDEFGHIJKLMNOPQRSTUVWXYZ");
alphaIndexer.setCursor(mCursor);
}
#Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
return doQuery();
}
}
Sometimes Android will hide the fast-scroll functionality if your list isn't long enough to warrant fast-scrolling. Not sure if that's your problem, but it might be worth trying to add a bunch of items to the list.
I've just lost couple of hours on alphabet indexer and fast scroller. In my case the list wasn't always long enough to warant the fast scroll/alphabet indexer feature. The exact behavior can be found in class FastScroller which is a helper class for AbsListView. There is a piece of code there that decides if "the list is long"
final boolean longList = childCount > 0 && itemCount / childCount >= MIN_PAGES;
MIN_PAGES is defined with value of 4. There you have it, if your list item count is not at least 4x the child count (visible rows) fast scroller and thus alphabet indexer will not appear.

Categories

Resources