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.
Related
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!
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...
My listfragment has a custom CursorAdapter that implements the SectionIndexer. I use the AlphabetIndexer helper class to implement the SectionIndexer in a simple standard way. But for some reason, while scrolling, the character in the scroll changes from K to R, skipping all the letters in between. So even though there are dozens of entries starting with L,M,N,O,P,Q, they all are coming under the section K. If i remove the K from the alphabet parameter of the AlphabetIndexer, the same behaviour persist with L. Any idea why this could happen?
private class ContactsAdapter extends CursorAdapter implements SectionIndexer {
private AlphabetIndexer mAlphabetIndexer; // Stores the AlphabetIndexer instance
...
public ContactsAdapter(Context context) {
...
final String alphabet = context.getString(R.string.alphabet); // alphabet=" ABCDEFGHIJKLMNOPQRSTUVWXYZ"
mAlphabetIndexer = new AlphabetIndexer(null, ContactsQuery.SORT_KEY, alphabet);
/**
* An override of getCount that simplifies accessing the Cursor. If the Cursor is null,
* getCount returns zero. As a result, no test for Cursor == null is needed.
*/
#Override
public int getCount() {
if (getCursor() == null) {
return 0;
}
return super.getCount();
}
#Override
public Cursor swapCursor(Cursor newCursor) {
// Update the AlphabetIndexer with new cursor as well
mAlphabetIndexer.setCursor(newCursor);
return super.swapCursor(newCursor);
}
#Override
public Object[] getSections() {
return mAlphabetIndexer.getSections();
}
/**
* Defines the SectionIndexer.getPositionForSection() interface.
*/
#Override
public int getPositionForSection(int i) {
if (getCursor() == null) {
return 0;
}
return mAlphabetIndexer.getPositionForSection(i);
}
/**
* Defines the SectionIndexer.getSectionForPosition() interface.
*/
#Override
public int getSectionForPosition(int i) {
if (getCursor() == null) {
return 0;
}
return mAlphabetIndexer.getSectionForPosition(i);
}
}
}
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.
Is it possible to use a SectionIndexer with a GridView in Android? Fast scroll is working fine, and I'm using a custom adapter that extends BaseAdapter. The adapter is currently implementing SectionIndexer and seems to be identical to the examples shown online and on Stack Overflow. This made me think if it's even possible to do with a GridView and a custom adapter.
static class YOUR_ADAPTER extends SimpleCursorAdapter implements SectionIndexer {
private AlphabetIndexer mIndexer;
YOUR_ADAPTER (Context context, AlbumBrowserActivity currentactivity,
int layout, Cursor cursor, String[] from, int[] to) {
super(context, layout, cursor, from, to);
getColumnIndices(cursor);
}
private void getColumnIndices(Cursor cursor) {
if (cursor != null) {
YOUR_COLUMN = cursor.getColumnIndexOrThrow(WHAT_YOU'RE_SORTING);
if (mIndexer != null) {
mIndexer.setCursor(cursor);
} else {
mIndexer = new AlphabetIndexer(cursor, YOUR_COLUMN, mResources.getString(
R.string.fast_scroll_alphabet));
}
}
}
#Override
public Object[] getSections() {
return mIndexer.getSections();
}
#Override
public int getPositionForSection(int section) {
return mIndexer.getPositionForSection(section);
}
#Override
public int getSectionForPosition(int position) {
return 0;
}
}
fast_scroll_alphabet String
<string name="fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
That's a basic example, but there's not much more to it than that. Implementing SectionIndexer is pretty simple.