Create asynchronous ContentProvider for Actionbar

2019-02-13 20:50发布

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>

2条回答
我欲成王,谁敢阻挡
2楼-- · 2019-02-13 21:08

Don't know if people still need this. Just in case, for future searchers, I found a solution for this. I also used Volley for my content provider class, which seemed not to fit naturally into Android's content provider framework. As opposed to muetzenflo's answer, I found that my content provider DOES run in the UI thread. Therefore, when I used Volley's Future synchronously in it, it slowed down (blocked) the UI until the request returned (timeout). In addition to this, I found information around the web that Volley's Future request should be run in other thread, such as in an Async task, for it to work well. Thus, it didn't solve my problem, because if I'd have to use it (in an async task), i would have used Volley's normal (async) request in the first place instead (which was what I used then). What I did was this:

  1. In my ContentProvider subclass, I define a listener interface:

    public interface ResultListener {

      void onProviderResult(Cursor mCursor);
    
      void onProviderError(String errorMsg);
    

    }

  2. In my activity (which implemented LoaderCallbacks), I implemented also above interface.

  3. In my singleton application class, I define a static variable which is used as transient data, together with its getter/setter methods:

    private static HashMap transientData = new HashMap();

    public static Object getTransientData(String objectName) {

    return transientData.get(objectName);
    

    }

    public static void setTransientData(String objectName, Object object) {

    transientData.put(objectName, object);
    

    }

  4. now the logic: In the activity, before calling getSupportLoaderManager().initLoader(...), I called MyApplication.setTransientData("requestor", this) :

In my content provider's class, in the volley request's onResponse callback, I did this:

public void onResponse(JSONArray response){

  ...

  ResultListener requestor =   (ResultListener)TheApplication.getTransientData("requestor");

  if (requestor!=null) requestor.onProviderResult(mCursor);

}

So that when the volley request returned, it will trigger the requestor's callback method, passing it the cursor filled with data from the response, and in turn the requestor (the activity) notified the cursor adapter by calling: adapter.swapCursor(c); adapter.notifyDataSetChanged();

Hope this helps someone. Be blessed.

查看更多
来,给爷笑一个
3楼-- · 2019-02-13 21:22

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.

查看更多
登录 后发表回答