问题
I am fairly new to android and I have some problems with a filtered listView and The activity it's in changing from landscape mode to portrait mode or or vice versa. I have an editText that I use for filtering "drinkSearch", this filtering works as long as I do not change the viewing angle (portrait vs landscape). This is the error that I get:
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteQuery: SELECT _id, name FROM drinks
As you can see in the following code I use the interface LoaderManager.LoaderCallbacks, this concept is kinda new for me and I am not sure where things go wrong. I would appreciate all help, thanks in advance!
public class Drinks_Fragment extends Fragment implements LoaderManager.LoaderCallbacks {
private static final int DRINKS_LIST_LOADER = 0x01;
private SimpleCursorAdapter adapter;
private ListView drinksList;
private String LOG;
private EditText drinkSearch;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.drinks_list, container, false);
drinkSearch = (EditText)view.findViewById(R.id.drinkInputSearch);
drinksList = (ListView) view.findViewById(R.id.drinksList);
drinksList.setEmptyView(view.findViewById(R.id.empty_list_view));
String[] from = {DrinksTable.COLUMN_NAME};
int[] to = {R.id.drinkName};
getLoaderManager().initLoader(DRINKS_LIST_LOADER, null, this);
adapter = new SimpleCursorAdapter(getActivity().getApplicationContext(), R.layout.drinks_list_item,null, from, to, 0);
drinksList.setAdapter(adapter);
In this part I ask my contentProvider for a new Cursor based on the string entered in the searchDrink editText. (Following code, until "return view" is just below the part above, same onCreateView method)
drinkSearch.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int arg1, int arg2, int arg3) {
// When user changed the Text
adapter.getFilter().filter(s.toString());
}
@Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
@Override
public void afterTextChanged(Editable arg0) {
// TODO Auto-generated method stub
}
});
adapter.setFilterQueryProvider(new FilterQueryProvider() {
public Cursor runQuery(CharSequence constraint) {
String value = "%"+constraint.toString()+"%";
ContentResolver content = getActivity().getContentResolver();
return content.query(CupProvider.DRINKS_URI,new String[]{DrinksTable.COLUMN_ID,DrinksTable.COLUMN_NAME},DrinksTable.COLUMN_NAME + " LIKE ?",new String[]{value},null);
}
});
return view;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu,v,menuInfo);
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.drink_actions,menu);
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
String[] projection = {DrinksTable.COLUMN_ID, DrinksTable.COLUMN_NAME};
CursorLoader cursorLoader = new CursorLoader(getActivity(), CupProvider.DRINKS_URI, projection, null, null, null);
return cursorLoader;
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
adapter.swapCursor(cursor);
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
// data is not available anymore, delete reference
adapter.swapCursor(null);
}
}
Here are 2 pictures to show how it looks at the moment: http://oi42.tinypic.com/dfc702.jpg http://oi43.tinypic.com/2ylqkqa.jpg
回答1:
Your code is a bit hard to make sense of due to poor formatting.
Anyway, the supplied answer is actually not a fix. The cursor returned at onLoadFinished
should be guaranteed not to be closed, so you're loading your cursor in the wrong manner. Specifically, when you call
adapter.getFilter().filter(s.toString());
I don't really understand what goes on here, but I do understand that you should do something else. Just store the query filter in a field within your Fragment
and run getLoaderManager().restartLoader(DRINKS_LIST_LOADER, null, this);
. Note that you run restartLoader
, and not initLoader
, because you have different data that you want to query for.
In your onCreateLoader
, you should use the filter that you stored as an instance variable for the selection
.
Some background
initLoader
loads the data that was loaded in the last run, if it had run before. This is why you call in in the initialization method of your Fragment/Activity. This is handy because you won't have to requery on orientation change.
restartLoader
cleans up previously loaded data so that you get a new Loader
to work with (likely) different data.
If you aren't really sure what you're doing still, make sure to read this article, which is a very good introductory article on Loaders with sample code that looks very much like what you want to achieve. Loaders are pretty enigmatic at first, but once you get the hang of it it's smooth sailing.
回答2:
Fixed it:
@Override public void onLoadFinished(Loader cursorLoader, Cursor cursor) { if(!cursor.isClosed()){ adapter.swapCursor(cursor); } }
I do have another problem now, my list does not get updated automatically...
回答3:
Adding this line in AndroidManifest helped me in the same situation:
android:configChanges="keyboardHidden|orientation|screenSize"
回答4:
This is because after filtering, the original cursor is closed because changeCursor is called. Overriding changeCursor with:
super.swapCursor(cursor)
in your SimpleCursorAdapter makes sure the original cursor is managed by the CursorLoader.
来源:https://stackoverflow.com/questions/17458251/android-attempt-to-re-open-an-already-closed-object-sqlitequery-using-loaderm