Создайте асинхронный ContentProvider для панели действий SearchView

У меня есть SearchView в моем ActionBar, который связан с ContentProvider, чтобы предлагать поисковые предложения. Эти предложения исходят не от БД (как обычно с ContentProvider), а от веб-службы. Вот почему я должен асинхронно обрабатывать курсор ContentProvider. Мой код работает до сих пор, но поисковые предложения всегда отстают на одну букву:

После ввода "the" я получаю все результаты предыдущего поиска => "th"

После ввода "they" я получаю все результаты предыдущего поиска => "the"

Как я могу сообщить SearchView, что у курсора есть новые результаты? Я просмотрел ContentObserver и ContentResolver().notifyChange(), но их невозможно использовать в контексте SearchView.

Вот мой код до сих пор. Важная часть находится в обратном вызове onResponse ContentProvider. Я создаю новый MatrixCursor и использую его для переопределения члена MatrixCursor.

AutocompleteSuggestionProvider расширяет 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;
}

Манифест Android

<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" />

поиск.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>

person muetzenflo    schedule 01.07.2014    source источник


Ответы (2)


Не знаю, нужно ли это людям. На всякий случай, для будущих искателей, я нашел решение для этого. Я также использовал Volley для своего класса контент-провайдеров, который, казалось, не вписывался естественным образом в структуру контент-провайдеров Android. В отличие от ответа muetzenflo, я обнаружил, что мой поставщик контента ДЕЙСТВИТЕЛЬНО работает в потоке пользовательского интерфейса. Поэтому, когда я синхронно использовал в нем Volley's Future, он тормозил (блокировал) UI до тех пор, пока не вернулся запрос (таймаут). В дополнение к этому я нашел в Интернете информацию о том, что запрос Volley Future должен выполняться в другом потоке, например, в асинхронной задаче, чтобы он работал хорошо. Таким образом, это не решило мою проблему, потому что, если бы мне пришлось использовать его (в асинхронной задаче), я бы в первую очередь использовал обычный (асинхронный) запрос Volley (что я и использовал тогда). Я сделал следующее:

  1. В моем подклассе ContentProvider я определяю интерфейс слушателя:

    открытый интерфейс ResultListener {

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

    }

  2. В моей деятельности (которая реализовала LoaderCallbacks) я также реализовал интерфейс выше.

  3. В моем классе одноэлементного приложения я определяю статическую переменную, которая используется в качестве временных данных вместе с ее методами получения/установки:

    private static HashMap transientData = new HashMap();

    общедоступный статический объект getTransientData (String objectName) {

    return transientData.get(objectName);
    

    }

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

    transientData.put(objectName, object);
    

    }

  4. теперь логика: в действии перед вызовом getSupportLoaderManager().initLoader(...) я вызвал MyApplication.setTransientData("requestor", this) :

В классе моего поставщика контента в обратном вызове onResponse запроса залпа я сделал следующее:

public void onResponse (ответ JSONArray) {

  ...

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

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

}

Таким образом, когда запрос залпа возвращается, он запускает метод обратного вызова запрашивающей стороны, передавая ему курсор, заполненный данными из ответа, и, в свою очередь, запрашивающая сторона (активность) уведомляет адаптер курсора, вызывая: adapter.swapCursor(c); адаптер.notifyDataSetChanged();

Надеюсь, это поможет кому-то. Благословенна.

person fiveloaves    schedule 30.04.2016
comment
и вот наступает 2021 год, а это решение все еще работает. Что касается меня, я сделал это через Intents. - person djdance; 30.12.2020
comment
@djdance Я больше не в теме. Должен ли я отметить это как лучший ответ? - person muetzenflo; 30.12.2020
comment
@muetzenflo Думаю, да. По крайней мере, это помогло мне понять, что других вариантов нет, и я не дурак, пытающийся идти тем же путем. Я не нашел альтернатив. ContentProvider -> HTTP-запрос -> singleton -> намерение -> swapCursor. - person djdance; 30.12.2020

Я нашел решение. Самое важное, что нужно знать, это то, что метод запроса ContentProvider НЕ работает в UI-потоке. Поэтому мы можем сделать синхронный вызов HTTP. Поскольку в наши дни каждый здравомыслящий человек использует Volley, вы должны сделать этот вызов следующим образом:

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();
}

Таким образом, все работает нормально.

person muetzenflo    schedule 12.07.2014
comment
Серьезно, мы в 2015 году, и нам все еще нужно много стандартного кода, чтобы делать такие простые вещи, как поиск на Android. Это сводит меня с ума. Спасибо за ваш пост. - person eVoxmusic; 18.01.2015