Autocompletetextview Backed by Cursorloader

AutoCompleteTextView with CursorLoader and SimpleCursorAdapter

After some investigation, it seems that SimpleCursorAdapter inherits from ResourceCursorAdapter, which inherits from CursorAdapter. CursorAdapter uses CursorFilter for filtering, and this class calls changeCursor() in its publishResults(). changeCursor closes the old cursor... So that's why my cursors were closed automatically.

I dropped the loaders, and changed the implementation to the code below, and it works greatly:

mAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1,
mTagDbLoader.fetchAll(), new String[] { DbConstants.Tags.KEY_TAG },
new int[] { android.R.id.text1 }, 0);

mAdapter.setFilterQueryProvider(new FilterQueryProvider() {

@Override
public Cursor runQuery(CharSequence constraint) {

if (constraint == null || constraint.equals(""))
return mAdapter.getCursor();

return mTagDbLoader.fetchContainingString(constraint.toString());
}
});

mAdapter.setCursorToStringConverter(new CursorToStringConverter() {

@Override
public CharSequence convertToString(Cursor c) {

return c.getString(c.getColumnIndexOrThrow(DbConstants.Tags.KEY_TAG));
}
});

AutoCompletetextView with CursorLoader

This is a fine approach, according to the android developer website :

Restarting a Loader :

To discard your old data, you use restartLoader(). For example, this implementation of SearchView.OnQueryTextListener restarts the loader when the user's query changes. The loader needs to be restarted so that it can use the revised search filter to do a new query:

Migrating to CursorLoader & LoaderManager for AutoCompleteTextView with SQLiteDatabase

Unfortunately, here on SO nobody came up with a solution, yet a colleague (he doesn't have an SO account) made a pull request with the migration fixes. It works perfectly, and I decided to post the correct answer here as well.

If you're interested in how these improvements look inside the actual code, you're welcome to visit the original open source project page on GitHub: Open Weather App

This is the new adapter:

mAdapter = new SimpleCursorAdapter(this,
R.layout.dropdown_text,
null,
new String[]{CITY_COUNTRY_NAME},
new int[]{R.id.text},0);
mAdapter.setFilterQueryProvider(new FilterQueryProvider() {
@Override
public Cursor runQuery(CharSequence constraint) {
if (constraint != null) {
if (constraint.length() >= 3 && !TextUtils.isEmpty(constraint)) {
Bundle bundle = new Bundle();
String query = charArrayUpperCaser(constraint);
bundle.putString(CITY_ARGS, query);
getLoaderManager().restartLoader(0, bundle, MainActivity.this).forceLoad();
}
}
return null;
}
});

This is the onCreateLoader() callback:

@Override
public android.content.Loader<Cursor> onCreateLoader(int id, Bundle args) {
String s = args.getString(CITY_ARGS);
WeatherCursorLoader loader = null;
if (s != null && !TextUtils.isEmpty(s)) {
loader = new WeatherCursorLoader(this, database, s);
}
return loader;
}

Custom CursorLoader itself:

private static class WeatherCursorLoader extends CursorLoader {

private SQLiteDatabase mSQLiteDatabase;
private String mQuery;

WeatherCursorLoader(Context context, SQLiteDatabase cDatabase, String s) {
super(context);
mSQLiteDatabase = cDatabase;
mQuery = s + "%";
Log.d(TAG, "WeatherCursorLoader: " + mQuery);
}


@Override
public Cursor loadInBackground() {
return mSQLiteDatabase.query(TABLE_1, mProjection,
CITY_COUNTRY_NAME + " like ?", new String[] {mQuery},
null, null, null, "50");
}
}

AutoCompleteTextView populated by contacts using CursorLoader

Since no one else has offered an answer, let me suggest populating your array in onLoaderFinish after the cursor has been created. The Cursor Loader is essentially a query statement and not the results, which is why you can't cast from a CursorLoader to a Cursor.

Using CursorLoader to query SQLite DB and populate AutoCompleteTextView

I know this is an old question but for people who visit this page:

SimpleCursorAdapter has a new constructor:

SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags)

this cunstructor does not use UI thread. You can use it safey.

IllegalStateException - Support LoaderManager with AutocompleteTextView

OnLoadFinished seems to get called sometimes with a dead cursor - if you put a test for isClosed() on the cursor you get passed you'll find it's closed in one in (some large number) attempts.

Unfortunately the 'standard' code to put in OnLoadFinished immediately does a changeCursor() on the adapter and, well, what follows is chaos, stackdumps, pestilence, etc.

My solution isn't any prettier than your try/catch.. ignore the bogus OnLoadFinished and risk the end user getting a blank UI.

AutoCompleteTextView in android

You are likely going to have write your own Filter and attach it via a TextWatcher. This response has an example of regex in an AutoCompleteTextView: Android AutoCompleteTextView with Regular Expression? and here is another regex/java example: How can I perform a partial match with java.util.regex.*?

EDIT: You will need to extend ArrayAdapter in order to override getFilter() and return your custom filter.

So you are going to have something like this:

autoCompleteTextView.setAdapter(arrayAdapter);
autoCompleteTextView.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
arrayAdapter.getFilter().filter(s);
}
});

public class RegexFilter extends Filter{

ArrayAdapter<String> mAdapter;
public RegexFilter(ArrayAdapter<String> adapter) {
mAdapter = adapter;
}
...
@Override
protected FilterResults performFiltering(CharSequence constraint) {
Pattern p = Pattern.compile(constraint);
Matcher m = p.matcher("");
List listOfMatches = new ArrayList<String>();
for (String curMonth : months) {
m.reset(curMonth);
if (m.matches || m.hitEnd()) {
listOfMatches.add(curMonth);
}
}
FilterResults results = new FilterResults();
results.values = listOfMatches;
results.count = listOfMatches.size();
return results;
}

@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
mAdapter.addAll(results.values);
mAdapter.notifyDataSetChanged();
}
}

public class PartialArrayAdapter extends ArrayAdapter<String> {
...
RegexFilter mFilter;
@Override
public TimedSuggestionFilter getFilter() {
if(null == mFilter)
mFilter = new RegexFilter(this);
return mFilter;
}


Related Topics



Leave a reply



Submit