问题
I'm trying to implement SearchView in a Fragment, with search results queried for directly from sqlite via CursorLoader and search results rendered in the same Fragment via custom CursorAdapter. Search suggestions are also queried for directly from sqlite and rendered via custom CursorAdapter (CountrySuggestionsAdaptor). So, I have one CursorLoader for getting suggestions(loader id =0) and the other CursorLoader for getting search results(loader id=1). There are 3 problems with loading suggestions at the moment:
1) Typing the first letter doesn't show any suggestions, i.e. it doesn't call bindView of the custom CursorAdapter. It only starts showing suggestions after second typing
2)If I type "Un" it will give suggestions, then if I type "Ur" it will give this error
android.database.StaleDataException: Attempting to access a closed CursorWindow.Most probable cause: cursor is deactivated prior to calling this method. at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:139) at android.database.AbstractWindowedCursor.getLong(AbstractWindowedCursor.java:74) at android.support.v4.widget.CursorAdapter.getItemId(CursorAdapter.java:226) at android.widget.AutoCompleteTextView.buildImeCompletions(AutoCompleteTextView.java:1132) at android.widget.AutoCompleteTextView.showDropDown(AutoCompleteTextView.java:1091) at android.widget.AutoCompleteTextView.updateDropDownForFilter(AutoCompleteTextView.java:974) at android.widget.AutoCompleteTextView.onFilterComplete(AutoCompleteTextView.java:956) at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:285) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:135) at android.app.ActivityThread.main(ActivityThread.java:5294) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:699)
3) First I type "Un" it gives suggestions, I click on the first suggestion and it successfully renders me a list based on it. Then I additionally type the third letter into search view, such as "Uni" and it crashes giving me this letter:
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteQuery: Select _id,countryNameRu, countryId FROM countries WHERE countryNameRu LIKE 'Un%' at android.database.sqlite.SQLiteClosable.acquireReference(SQLiteClosable.java:55) at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:58) at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:152) at android.database.sqlite.SQLiteCursor.onMove(SQLiteCursor.java:124) at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:214) at android.support.v4.widget.CursorAdapter.getItemId(CursorAdapter.java:225) at android.widget.AdapterView.rememberSyncState(AdapterView.java:1226) at android.widget.AdapterView$AdapterDataSetObserver.onChanged(AdapterView.java:820) at android.widget.AbsListView$AdapterDataSetObserver.onChanged(AbsListView.java:6156) at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37) at android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50) at android.support.v4.widget.CursorAdapter.swapCursor(CursorAdapter.java:347) at android.support.v4.widget.CursorAdapter.changeCursor(CursorAdapter.java:315) at android.support.v4.widget.CursorFilter.publishResults(CursorFilter.java:68) at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:282) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:135) at android.app.ActivityThread.main(ActivityThread.java:5294) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:699)
I've been looking for solutions all over the internet for several days now, I'm kinda desperate at the moment. I thought about implementing ContentProvider, but how would it help and is it absolutely necessary?
The fragment:
import android.database.Cursor;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.widget.SearchView;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Callback;
public class RoamingTariffs extends ProgressListFragment implements LoaderManager.LoaderCallbacks<Cursor>{
private RoamingTariffSearchResultsAdapter roamingTariffAdapter;
private CountrySuggestionsAdaptor mSearchViewAdapter;
RoamingTariffDbHelper dbHelper;
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.roaming_toolbar, menu);
mSearchViewAdapter = new CountriesSearchResultsAdaptor(this.getActivity(),null);
SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
searchView.setSuggestionsAdapter(mSearchViewAdapter);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener(){
@Override
public boolean onQueryTextSubmit(String s) {
if(!s.isEmpty())
loadSuggestions(s,false);
return true;
}
@Override
public boolean onQueryTextChange(String s) {
if(!s.isEmpty())
loadSuggestions(s,false);
return true;
}
});
searchView.setOnSuggestionListener(
new SearchView.OnSuggestionListener(){
@Override
public boolean onSuggestionSelect(int position) {
Cursor cursor = (Cursor) mSearchViewAdapter.getItem(position);
String countryId1 = cursor.getString(1);
loadCountryDetails(countryId1);
return true;
}
@Override
public boolean onSuggestionClick(int position) {
Cursor cursor = (Cursor) mSearchViewAdapter.getItem(position);
String countryId1 = cursor.getString(1);
loadCountryDetails(countryId1);
return false;
}
}
);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
roamingTariffAdapter = new RoamingTariffCursorAdapter(getActivity(), null);
getListView().setAdapter(roamingTariffAdapter);
dbHelper = new RoamingTariffDbHelper(getActivity());
((LoginActivity) getActivity()).getSupportActionBar().setTitle("Roaming Tariffs");
RoamingTariffInterface roamingTariffService =
RoamingTariffClient.getClient().create(RoamingTariffInterface.class);
Call<List<CountryDto>> call = roamingTariffService.getCountries(countryId);
call.enqueue(new Callback<List<CountryDto>>() {
@Override
public void onResponse(Call<List<CountryDto>> call, Response<List<CountryDto>> response) {
List<CountryDto> countryList = response.body();
if (countryList == null)
countryList = new ArrayList<CountryDto>();
// CREATE SQLITE TABLE AND SAVE
RoamingTariffDbHelper helper = new RoamingTariffDbHelper(getActivity());
for (CountryDto countryDto : countryList) {
helper.addCountryEntry(countryDto);
}
}
@Override
public void onFailure(Call<List<CountryDto>> call, Throwable t) {
showError();
}
});
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
showProgress();
CursorLoader cursorLoader = null;
switch (id){
case 0://loadSuggestions
final String queryText = args.getString("queryText");
cursorLoader = new CursorLoader(getActivity()) {
@Override
public Cursor loadInBackground() {
Cursor cursor = dbHelper.getCountryNamesBySearchLetters(queryText);
return cursor;
}
};
break;
case 1://load results
final String countryId = args.getString("countryId");
cursorLoader = new CursorLoader(getActivity()) {
@Override
public Cursor loadInBackground() {
Cursor cursor = dbHelper.getOperatorsByCountryId(countryId);
return cursor;
}
};
break;
}
return cursorLoader;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
int loaderId = loader.getId();
switch(loaderId){
case 0://suggestions are ready to show
if (data != null)
mSearchViewAdapter.swapCursor(data);
break;
case 1://search results are ready to show
if(data !=null){
roamingTariffAdapter.swapCursor(data);}
break;
}
showContent();
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mSearchViewAdapter.swapCursor(null);
roamingTariffAdapter.swapCursor(null);
}
public void loadSuggestions(String query){
Bundle bundle = new Bundle();
bundle.putString("queryText", query);
getLoaderManager().restartLoader(0, bundle, this);
}
public void loadCountryDetails(String countryId){
Bundle bundle = new Bundle();
bundle.putString("countryId", countryId);
getLoaderManager().restartLoader(1, bundle, this);
}
...
}
The suggestion adapter:
public class CountrySuggestionsAdaptor extends CursorAdapter {
private Context context = null;
public CountrySuggestionsAdaptor(Context context, Cursor c) {
super(context, c, 0);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.country_suggestion_item,parent, false);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView countryView = (TextView)view.findViewById(R.id.country_item);
countryView.setText(cursor.getString(1));
}
}
The relevant query method from RoamingTariffDbHelper:
public Cursor getCountryNamesBySearchLetters(String startingStr){
SQLiteDatabase db = this.getReadableDatabase();
String query = RoamingTariffDbContract.SQL_SELECT_COUNTRIES_START_WITH+
Helper.firstLetterToCapital(startingStr)+"%'";
Cursor cursor = db.rawQuery(query,null);
if(cursor == null){
return null;
}
else if(!cursor.moveToFirst()) {
cursor.close();
return null;
}
return cursor;
}
Edit: I followed @pskink's advice and removed querytextlisteners and added this instead
FilterQueryProvider fqp =new FilterQueryProvider() {
@Override
public Cursor runQuery(CharSequence constraint) {
Cursor cursor = null;
if(constraint.length()!=0)
cursor = dbHelper.getCountryNamesBySearchLetters(constraint.toString());
return cursor;
}
};
Problems 2 and 3 are gone. Problem number 1 persists. I've debugged inside runQuery, on the first type constraint is null for some reason, hence no suggestions. Although constraint should be the first letter I've typed. What's the reason?
来源:https://stackoverflow.com/questions/39137353/searchview-implementation-via-cursoradapter-and-cursorloader-swapcursor-is-givi