I have a SearchView in my ActionBar which is connected with a ContentProvider to give search suggestions. These suggestions do not come from a DB (as usual with ContentProvider), but from a web service. That's why I have to handle the Cursor of the ContentProvider asyncronously. My code works so far, but the search suggestions are always one letter "behind":
After I enter "the"
, I get all results from the previous search => "th"
After I enter "they"
, I get all results from the previous search => "the"
How can I tell the SearchView that the Cursor has new results in it? I looked into ContentObserver and ContentResolver().notifyChange(), but they are not really possible to use in context of the SearchView.
Here's my code so far. The important part is in the onResponse-callback of the ContentProvider. I create a new MatrixCursor and use it to override the member MatrixCursor.
AutocompleteSuggestionProvider extends ContentProvider
@Override
public Cursor query(final Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String query = selectionArgs[0];
mNetworkHelper.startAutoCompleteRequest(
selectionArgs[0],
SuggestionCategory.EVERYTHING,
new Response.Listener<AutoCompleteResponse>() {
/**
* This is the callback for a successful web service request
*/
@Override
public void onResponse(AutoCompleteResponse response) {
MatrixCursor nCursor = new MatrixCursor(SEARCH_SUGGEST_COLUMNS, 10);
List<String> suggestions = response.getResults();
// transfrom suggestions to MatrixCursor
for (int i = 0; i < suggestions.size() && i < 10; i++)
nCursor.addRow(new String[]{String.valueOf(i), suggestions.get(i)});
}
// update cursor
mAsyncCursor = nCursor;
}
},
/**
* This is the callback for a errornous web service request
*/
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(getContext(), "Fehler", Toast.LENGTH_SHORT).show();
}
}
);
return mAsyncCursor;
}
AndroidManifest
<activity
android:name=".activities.MainActivity"
android:label="@string/app_name"
android:launchMode="singleTop"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data android:name="android.app.default_searchable" android:value=".MainActivity" />
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/>
</activity>
<provider
android:name=".provider.AutocompleteSuggestionProvider"
android:authorities="my.package.provider.AutocompleteSuggestion"
android:exported="false" />
searchable.xml
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_name"
android:hint="@string/search_hint"
android:searchSettingsDescription="@string/search_settings"
android:searchSuggestAuthority="my.package.provider.AutocompleteSuggestion"
android:searchSuggestIntentAction="android.intent.action.VIEW"
android:searchSuggestSelection=" ?"
android:searchSuggestThreshold="2" >
</searchable>
I found the solution. The most important thing to know ist that the query-method of a ContentProvider does NOT run on the UI-thread. Therefore we can do a synchronous HTTP call. Since every sane person uses Volley these days you have to do this call like this:
String url = NetworkHelper.buildRequestUrl(selectionArgs[0], SuggestionCategory.EVERYTHING, RequestType.AUTOCOMPLETE);
RequestFuture<JSONArray> future = RequestFuture.newFuture();
JsonArrayRequest request = new JsonArrayRequest(url, future, future);
mNetworkHelper.getRequestQueue().add(request);
// this does not run on the UI thread, so we can make a synchronous HTTP request
try {
JSONArray suggestions = future.get();
MatrixCursor resultCursor = new MatrixCursor(SEARCH_SUGGEST_COLUMNS, 10);
for (int i = 0; i < suggestions.length() && i < 10; i++) {
resultCursor.addRow(new String[]{String.valueOf(i), suggestions.get(i).toString()});
}
return resultCursor;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
This way everything works fine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With