SearchView implementation via CursorAdapter and CursorLoader. swapCursor is giving AStaleDataException

孤街醉人 提交于 2019-12-11 06:25:43

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!