#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 (что я и использовал тогда). Что я сделал, так это:
-
В моем подклассе ContentProvider я определяю интерфейс прослушивателя:
открытый интерфейс ResultListener {
void onProviderResult(Cursor mCursor); void onProviderError(String errorMsg);
}
-
В моей деятельности (которая реализовала обратные вызовы загрузки) я также реализовал вышеупомянутый интерфейс.
-
В моем классе одноэлементного приложения я определяю статическую переменную, которая используется в качестве временных данных, вместе с ее методами получения / установки:
частная статическая хеш-карта transientData = новая хеш-карта();
общедоступный статический объект getTransientData(строковое имяобъЕкта) {
return transientData.get(objectName);
}
общедоступный статический void setTransientData(строковое имяобъекта, Object объект) {
transientData.put(objectName, object);
}
- теперь логика: в действии, перед вызовом 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.