Создать асинхронный поставщик контента для Actionbar SearchView

#android #asynchronous #android-contentprovider #searchview #search-suggestion

#Android #асинхронный #android-contentprovider #searchview #поиск-предложение

Вопрос:

У меня в ActionBar есть SearchView, который связан с 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() amp;amp; 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>
  

Ответ №1:

Я нашел решение. Самое важное, что нужно знать, это то, что метод запроса ContentProvider НЕ выполняется в потоке пользовательского интерфейса. Поэтому мы можем выполнить синхронный 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() amp;amp; 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();
}
  

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

Комментарии:

1. Серьезно, мы находимся в 2015 году, и нам все еще нужно много шаблонного кода для выполнения таких простых вещей, как поиск на Android.. Это сводит меня с ума. Спасибо за ваш пост.

Ответ №2:

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

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

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

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

    }

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

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

    частная статическая хеш-карта transientData = новая хеш-карта();

    общедоступный статический объект getTransientData(строковое имяобъЕкта) {

     return transientData.get(objectName);
      

    }

    общедоступный статический void setTransientData(строковое имяобъекта, Object объект) {

     transientData.put(objectName, object);
      

    }

  4. теперь логика: в действии, перед вызовом getSupportLoaderManager().initLoader(…), я вызвал MyApplication.setTransientData («запрашивающий», это) :

В классе моего контент-провайдера, в обратном вызове onResponse запроса volley, я сделал это:

общедоступный void onResponse (ответ JSONArray){

   ...

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

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

}

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

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

Комментарии:

1. и вот наступает 2021 год, и это решение все еще работает. Что касается меня, я сделал это с помощью намерений.

2. @djdance Я больше не в теме. Должен ли я отметить это как лучший ответ?

3. @muetzenflo я так думаю. По крайней мере, это помогло мне понять, что других вариантов нет, и я не дурак, пытающийся пойти тем же путем. Я не нашел альтернатив. ContentProvider -> http-запрос -> singleton -> intent -> swapCursor.