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.
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!
So, I have an activity with a TextView and a ListView with a custom BaseAdapter. This activity looks like this:
As you can see, every item of the list is a custom layout and the basic idea is: every time the numeric EditText within it changes, the "total" TextView from the activity (which is the sum of the prices of every product) must be updated as well.
I suppose it must somehow be done from the Adapter class, but I don't know how to do it.
My Activity file looks like this (it gets products data from server via "GetCollectionProducts" AsyncTask, where I set the adapter):
public class ProductAisleActivity extends AppCompatActivity implements View.OnClickListener{
ListView productList;
Button participate;
ImageButton search;
EditText searchET;
TextView productsTotal;
Product[] colProducts;
RelativeLayout collectionHeader;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_product_aisle);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
/* ...
Irrelevant code to this question
*/
productsTotal = (TextView) findViewById(R.id.products_aisle_total);
productsTotal.setText(
getResources().getString(
R.string.productsTotal,
String.valueOf(0.00)
)
);
productList = (ListView) findViewById(R.id.products_aisle_list);
new GetCollectionProducts().execute();
}
private class GetCollectionProducts extends AsyncTask<Void,Void,JSONArray>{
#Override
protected JSONArray doInBackground(Void... voids) {
/* Irrelevant code to this question */
}
#Override
protected void onPostExecute(JSONArray jsonArray) {
/* Irrelevant code to this question */
productList.setAdapter(
new CollectionProductsAdapter(
ProductAisleActivity.this,
colProducts
)
);
}
}
And my Adapter file looks as follows:
public class CollectionProductsAdapter extends BaseAdapter {
Context context;
ProductAisleActivity.Product[] data;
private static LayoutInflater inflater = null;
public CollectionProductsAdapter(Context context, ProductAisleActivity.Product[] data) {
this.context = context;
this.data = data;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return data.length;
}
#Override
public Object getItem(int i) {
return data[i];
}
#Override
public long getItemId(int i) {
return i;
}
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
View v = view;
if (v == null) {
v = inflater.inflate(R.layout.product_row_layout, null);
}
ProductAisleActivity.Product product = data[i];
/* ...
Irrelevant code to this question
*/
EditText productQuantity = (EditText) v.findViewById(R.id.productQuantity);
productQuantity.setText("0");
return v;
}
}
I'm stuck at this point, any help will be appreciated.
First you need to listen for any changes in the EditText so you can handle things dynamically without explicitly using something like a submit button. You can do this with a TextWatcher.
productQuanity.addTextChangedListener(new TextWatcher() {
private double originalCost = 0.0;
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Before we change the text, set the originalCost
// so we can know what the change is after the edit
originalCost = getCost(s.toString());
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// You don't need to utilize this method
}
#Override
public void afterTextChanged(Editable s) {
// After the change has taken place in the text,
// get the new cost and calculate the difference
double newCost = getCost(s.toString());
double changeInCost = newCost - originalCost;
}
private double getCost(String input){
String count = input.toString();
if(TextUtils.isEmpty(count))
return 0.0;
else
return (double) Integer.parseInt(count) * product.getPrice();
}
});
Now that we have the change in cost, what do we do with it? We need to notify the activity that we have a change. We can do that with an observer, which is fine, but for fun let's use an interface to implement a listener.
Modify your adapter class
public class CollectionProductsAdapter extends BaseAdapter {
public interface CostChangedListener{
void onCostChanged(double change);
}
Context context;
ProductAisleActivity.Product[] data;
private LayoutInflater inflater = null; // THIS SHOULDN'T BE STATIC
CostChangedListener listener;
public CollectionProductsAdapter(Context context, ProductAisleActivity.Product[] data, CostChangedListener listener) {
this.context = context;
this.data = data;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.listener = listener;
}
// The rest of your code
}
Now when we update the cost in our TextWatcher we can call
if(listener != null)
listener.onCostChanged(changeInCost);
Last, to make sure we utilize this correctly, we will need to pass a listener in our CollectionProductsAdapter constructor
productList.setAdapter(new CollectionProductsAdapter(
ProductAisleActivity.this, colProducts,
new CostChangeListener(){
#Override
public void onCostChanged(double change){
double currentTotal = Double.valueOf(productTotal.getText());
double newTotal = currentTotal + change;
productTotal.setText(String.valueOf(newTotal));
}));
Obviously you may need to tweak some of this to get it to match perfectly, and I haven't tested it so some things might be off a bit, but this should get you going in the right direction. If you have any issue feel free to comment and I will try to help you through it.
Notes
Do not keep a static reference like you were with your layout inflater
It is worth taking a look at the RecyclerView or at least the ViewHolder pattern with an Adapter
You want to add a textChangedListener for changing item values as user changes values in EditText.
You can use TextChangedListener here:
EditText myEditTextField = new EditText(this);
myEditTextField.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
}
});
You can perform tasks according to your need. It has 3 methods:
1.beforeTextChanged
2.onTextChanged
3.afterTextChanged
So you can get your task done by the help of "afterTextChanged". You have to simply call your method of calculating price for no. of items when user enters a particular number.And it will show you the price as you want.
Hope this help!
I have an app with a fairly standard fragment layout. An expandable listview fragment on the left and a panel on the right that is used for different things depending on what the user chooses to do with the list on the left (displaying data, adding new data, etc).
I'm using the LoaderManager (first time using loaders) with CommonWare's loaderex library as I have no need or desire to create a Content Provider for my database just so I can use a standard CursorLoader. This setup works great for displaying my list.
The issue I am having is when I use the second fragment to add data to the database. I cannot figure out how to trigger a re-load of the list in the first fragment. For the life of me I cannot figure out how to grab the loader from the first fragment in the second so that it will be aware that the data needs to be pulled again, nor can I seem to figure how to manually trigger a re-load.
As this is my first attempt at using Loaders, if I'm doing something improperly I'd be happy to be (gently) re-directed down a better path.
Fragment 1
public class StudentListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
private TAOpenHelper mDbHelper = null;
private MyExpandableListAdapter mAdapter = null;
private ExpandableListView lv = null;
private Button addStudentButton;
public static long mRowId = 0;
public SQLiteCursorLoader studentLoader=null;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.leftlistfragment_entry, container,
false);
return v;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
addStudentButton = (Button) getActivity().findViewById(R.id.AddButton);
addStudentButton.setText(getResources().getString(
R.string.button_add_student));
addStudentButton.setOnClickListener(addStudentButtonHandler);
lv = (ExpandableListView) getListView();
mDbHelper = TAOpenHelper.getInstance(getActivity());
fillData();
getLoaderManager().getLoader(-1);
if (studentLoader != null && !studentLoader.isReset()) {
getLoaderManager().restartLoader(-1, null, this);
} else {
getLoaderManager().initLoader(-1, null, this);
}
}
private void fillData() {
mAdapter = new MyExpandableListAdapter(getActivity(), this,
R.layout.listlayout_exp_double_group,
R.layout.listlayout_exp_double_child,
new String[] { TeacherAidDB.STUDENT_FIRST,
TeacherAidDB.STUDENT_LAST }, new int[] {
R.id.ListItem1, R.id.ListItem2 }, new String[] {
TeacherAidDB.CLASS_NAME, TeacherAidDB.CLASS_LEVEL },
new int[] { R.id.ListItem1, R.id.ListItem2 });
lv.setAdapter(mAdapter);
}
public class MyExpandableListAdapter extends SimpleCursorTreeAdapter {
protected final SparseIntArray mGroupMap;
private StudentListFragment mFragment;
public MyExpandableListAdapter(Context context,
StudentListFragment clf, int groupLayout, int childLayout,
String[] groupFrom, int[] groupTo, String[] childrenFrom,
int[] childrenTo) {
super(context, null, groupLayout, groupFrom, groupTo, childLayout,
childrenFrom, childrenTo);
mFragment = clf;
mGroupMap = new SparseIntArray();
}
#Override
protected Cursor getChildrenCursor(Cursor groupCursor) {
int groupPos = groupCursor.getPosition();
int groupId = groupCursor.getInt(groupCursor
.getColumnIndex(TeacherAidDB.CLASS_ROWID));
mGroupMap.put(groupId, groupPos);
Loader<Cursor> loader = getActivity().getLoaderManager().getLoader(
groupId);
if (loader != null && !loader.isReset()) {
getActivity().getLoaderManager().restartLoader(groupId, null,
mFragment);
} else {
getActivity().getLoaderManager().initLoader(groupId, null,
mFragment);
}
return null;
}
public SparseIntArray getGroupMap() {
return mGroupMap;
}
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id != -1) { // Child Cursor
studentLoader = new SQLiteCursorLoader(getActivity(), mDbHelper,
TeacherAidDB.STUDENT_LIST_CLASS_QUERY + id, null);
} else { // Group Cursor
studentLoader = new SQLiteCursorLoader(getActivity(), mDbHelper,
TeacherAidDB.STUDENT_LIST_QUERY, null);
}
return studentLoader;
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
int id = loader.getId();
if (id != -1) { // Child cursor
if (!data.isClosed()) {
SparseIntArray groupMap = mAdapter.getGroupMap();
int groupPos = groupMap.get(id);
mAdapter.setChildrenCursor(groupPos, data);
}
} else { // Groups cursor
mAdapter.setGroupCursor(data);
}
}
#Override
public void onLoaderReset(Loader<Cursor> arg0) {
mAdapter.changeCursor(null);
}
View.OnClickListener addStudentButtonHandler = new View.OnClickListener() {
public void onClick(View v) {
AddPerson personadd = AddPerson.newInstance(AddPerson.STUDENT, AddPerson.CREATE, mRowId);
getFragmentManager().beginTransaction()
.replace(R.id.rightpane, personadd).commit();
}
};
}
Fragment 2
public class AddPerson extends Fragment {
public static int STUDENT = 0;
public static int TEACHER = 1;
public static int CREATE = 0;
public static int EDIT = 1;
private int mRowId;
private TAOpenHelper mDbHelper;
private Cursor personedit;
private Button commit;
private Button cancel;
int who;
int what;
long rowId;
static AddPerson newInstance(int type, int action, long rowid) {
AddPerson f = new AddPerson();
Bundle args = new Bundle();
args.putInt("type", type);
args.putInt("action", action);
args.putLong("rowid", rowid);
f.setArguments(args);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
who = getArguments().getInt("type");
what = getArguments().getInt("action");
rowId = getArguments().getInt("rowid");
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.dialog_person_add, container, false);
mDbHelper = TAOpenHelper.getInstance(getActivity());
if (what == EDIT) {
if (who == STUDENT) {
// Student Edit stuff here
} else {
// Teacher Edit stuff here
}
} else {
if (who == STUDENT) {
// Student Create stuff here
} else {
// Teacher Create stuff here
}
}
// Code to gather data from user goes here
commit = (Button) v.findViewById(R.id.commitbutton);
commit.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
first = firstTxt.getText().toString();
last = lastTxt.getText().toString();
street = streetTxt.getText().toString();
city = cityTxt.getText().toString();
zip = zipTxt.getText().toString();
phone = phoneTxt.getText().toString();
email = emailTxt.getText().toString();
if (what == CREATE) {
processAdd(who);
} else {
processUpdate(who);
}
}
});
cancel = (Button) v.findViewById(R.id.cancelbutton);
cancel.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Fragment check = getFragmentManager().findFragmentById(
R.id.rightpane);
getFragmentManager().beginTransaction().remove(check).commit();
}
});
return v;
}
private void processAdd(int who) {
ContentValues initialValues = new ContentValues();
if (who == STUDENT) {
initialValues.put(TeacherAidDB.STUDENT_FIRST, first);
initialValues.put(TeacherAidDB.STUDENT_LAST, last);
initialValues.put(TeacherAidDB.STUDENT_STREET, street);
initialValues.put(TeacherAidDB.STUDENT_CITY, city);
initialValues.put(TeacherAidDB.STUDENT_STATE, state);
initialValues.put(TeacherAidDB.STUDENT_ZIP, zip);
initialValues.put(TeacherAidDB.STUDENT_PHONE, phone);
initialValues.put(TeacherAidDB.STUDENT_EMAIL, email);
initialValues.put(TeacherAidDB.STUDENT_BDAY, birthday);
// How to get studentLoader from fragment 1?
//studentLoader.insert(TeacherAidDB.STUDENT_TABLE, null, initialValues);
}
}
}
With a regular CursorLoader, this would happen automagically via the ContentObserver framework, which eventually boils down to a bunch of static data members.
With SQLiteCursorLoader, ContentObserver is not available, with the closest simulacrum being to route your CRUD operations through the Loader so it knows to reload the Cursor. And that is really only designed for use within a single activity.
So, as Luksprog suggested, your best option is to delegate CRUD work to the containing activity.
If these fragments might be hosted by disparate activities (e.g., for small/normal vs. large/xlarge screen sizes), define a common interface for handling this work, and have the fragments delegate to the interface.
I have a listView with a custom adapter.
I have an edit text view in my activity where the user can type to filter the content on the list view.
Lets say I have two items on the list view. After doing the filter, the ArrayList is reduced to one element.
The problem is that the getView method is still executed for the two previous rows.
Here is my code.
ListActivity:
public class TeamsListActivity extends ListActivity {
private ArrayList<Team> teams = new ArrayList<Team>();
private TeamsListAdapter teamsListAdapter = null;
private EditText searchInput = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.teams_list);
// get association id from the intent.
int afId = getIntent().getExtras().getInt("afId");
Log.i(MainActivity.TAG, "id_association =" + afId);
// get the teams from the association.
this.teams = TeamsDAO.getInstance(getApplicationContext())
.getTeamsByAssociation(afId);
// inits the list adapter
teamsListAdapter = new TeamsListAdapter(this, teams);
setListAdapter(teamsListAdapter);
// gets the search input view
searchInput = (EditText) findViewById(R.id.teams_list_search_box);
searchInput.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
public void afterTextChanged(Editable s) {
Log.d(Constants.TAG,"Teams search: " + s.toString());
teamsListAdapter.getFilter().filter(s);
}
});
}
}
My adapter
public class TeamsListAdapter extends ArrayAdapter<Team> implements Filterable {
private ArrayList<Team> teams = null;
private Context context = null;
public TeamsListAdapter(Context context,
ArrayList<Team> objects) {
super(context, R.layout.teams_list_row, objects);
this.context = context;
this.teams = objects;
}
#Override
public View getView(int position, View v, ViewGroup parent) {
if (v == null) {
LayoutInflater vi = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.teams_list_row, null);
}
Team t = teams.get(position);
if (t != null) {
TextView txtTeamName = (TextView) v.findViewById(R.id.teams_list_team_name);
txtTeamName.setText(t.getName());
ImageView ivTeamLogo = (ImageView) v.findViewById(R.id.teams_list_team_logo);
ivTeamLogo.setImageResource(R.drawable.af_braga);
}
return v;
}
#Override
public Filter getFilter() {
return new Filter() {
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
Log.d(Constants.TAG, "**** Search RESULTS for: " + constraint);
teams = (ArrayList<Team>) results.values;
Log.d(Constants.TAG,"size:"+teams.size());
TeamsListAdapter.this.notifyDataSetChanged();
}
#Override
protected FilterResults performFiltering(CharSequence constraint) {
Log.d(Constants.TAG, "**** PERFORM TEAK FILTERING for: " + constraint);
ArrayList<Team> filteredResults = getFilteredResults(constraint);
FilterResults results = new FilterResults();
results.values = filteredResults;
return results;
}
/**
* filters the teamsList
* #param constraint String The text to search
* #return ArrayList<Team>
*/
private ArrayList<Team> getFilteredResults(CharSequence constraint) {
ArrayList<Team> teams = TeamsListAdapter.this.teams;
ArrayList<Team> filteredTeams = new ArrayList<Team>();
for(int i=0;i< teams.size();i++){
if(teams.get(i).getName().toLowerCase().startsWith(constraint.toString().toLowerCase())){
filteredTeams.add(teams.get(i));
}
}
return filteredTeams;
}
};
}
}
Clarification of the problem.
1.Let´s say my list starts showing two elements.
2. The user inserts some text in the edit text to filter the list content which results in a updated arrayList with only one element who matches the query.
3. After called the notifyDataSetChanged the getView still is called like there was two rows in the dataset resulting in IndexOutOfBoundsException when executing Team t = teams.get(position) becuase the arrayList has only one element now so when position = 1 => the get(postion) fails.
Hope its clear now.
Well I fix my problem.
I needed to override a couple of methods in my adapter like this.
#Override
public int getCount() {
return teams.size();
}
#Override
public Team getItem(int position) {
return teams.get(position);
}
#Override
public long getItemId(int position) {
return teams.get(position).getId();
}
Now i have discoreved a problem with my search implementation. Because i am updating the original dataset the search only works one time. If I try to go back to original list i cant because my original data array has been modified. Easy to solve with with a database query instead of filtering the array list.
I think you dont need
teams = (ArrayList) results.values;
in publishResults function.
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.