java.lang.IllegalStateException: attempt to re-open an already-closed object - android

I'm trying to figure out why occasionally I'm getting the IllegalStateException. I can't find any good examples that show how to load a list using a thread to query a SQLite database. I've included my code below. Most of the time it works correctly, but occasionally, I'm getting the IllegalStateException.
I have also gotten a similar exception in another activity of mine that is an instance of ExpandableListActivity. That exception states "trying to requery an already closed cursor".
Can somebody tell me the correct way to do this so that it doesn't cause any errors? I would prefer to use the cursors instead of copying all of the data into memory. If I can't figure this out then I will have to load it all into memory.
I think the issue has something to do with startManagingCursor(Cursor) and the fact that the database connection is closed in onDestroy().
plz help
-- tale
public class MyListActivity extends ListActivity {
private MyCursorAdapter adapter;
private SQLiteDatabase db = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
new RetrieveCursorTask(this).execute((Void[]) null);
} catch (Exception e) {
}
}
#Override
protected void onDestroy() {
super.onDestroy();
// Null out the cursor.
if (adapter != null) {
adapter.changeCursor(null);
adapter = null;
}
if (db != null && db.isOpen()) {
db.close();
}
}
private class RetrieveCursorTask extends AsyncTask<Void, Void, Cursor> {
private Context ctx;
public RetrieveCursorTask(Context ctx) {
this.ctx = ctx;
}
#Override
protected Cursor doInBackground(Void... params) {
Cursor cursor = null;
DbHelper helper = new DbHelper(ctx);
try {
db = helper.getReadableDatabase();
cursor = db.query("users",
new String[] {
DbHelper.ID_COLUMN,
DbHelper.UID_COLUMN
},
null, null, null, null, null);
startManagingCursor(cursor);
} catch (Exception e) {
}
return cursor;
}
#Override
protected void onPostExecute(Cursor cursor) {
super.onPostExecute(cursor);
if (cursor != null) {
try {
adapter = new MyCursorAdapter(ctx, cursor);
} catch (Exception e) {
}
setListAdapter(adapter);
}
}
}
private class MyCursorAdapter extends CursorAdapter {
private Context ctx;
public MyCursorAdapter(Context context, Cursor c) {
super(context, c);
this.ctx = context;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
// ...
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
// ...
}
}
}

Look into AsyncQueryHandler if you want to query DB the way you want.
Your task RetrieveCursorTask is running on separate thread so when your activity gets destroyed your AsyncTask might still be running in background but as you have closed your cursor in main activity onDestroy it might be requeried again after your AsyncTask returns.

Sounds like you need to syncronize the block where you set your adapter in onPostExecute. The problem is since AsyncTask is running on a separate thread, the order in which the cursor is set and subsequently requested isn't guaranteed. Try this..
#Override
protected void onPostExecute(Cursor cursor) {
super.onPostExecute(cursor);
synchronized(anyObject) {
if (cursor != null) {
try {
adapter = new MyCursorAdapter(ctx, cursor);
} catch (Exception e) {
}
setListAdapter(adapter);
}
}
}

Related

RecyclerView blocks UI when setting the adapter

I have a requirement in which I have a populated database with over 300k rows. I have successfully implemented a CursorAdapter based in this question, with a mix of the two most up voted answers HERE.
I have implemented an AsyncTask for background service to perform the query to the database which is very fast, doesn't take more than 2-3 seconds. My ProgressDialog from the AsyncTask is at times hard to detect.
My problem is, when the task is done and I retrieve the Cursor, when I set the Adapter to the RecyclerView, the process freezes my UI for a few seconds until the data is set. It also happens when I perform a search (new query, same procedure as getting all the rows but with fewer rows), and replace the Cursor to update the data.
Here is some relevant code:
AsyncTask
#Override
protected Void doInBackground(Void... Void) {
if(type==Constants.GET_ZIP_CODES)
cursor = db.getAllZipCodes();
else
cursor = db.searchZipCodes(text);
return null;
}
#Override
protected void onPostExecute(Void Void) {
setAdapter();
mProgressDialog.dismiss();
super.onPostExecute(Void);
}
Methods
private void setAdapter(){
if(myAdapter == null){
myAdapter = new MyAdapter(getActivity(), cursor);
search_rv.setAdapter(myAdapter);
} else
myAdapter.swapCursor(cursor);
}
Since it is a search I don't have much to do here besides notifyDataSetChanged() because all the data changes widely in every search.
Is this normal? Since a RecyclerView only renders the visible views, why does it freeze and takes so long to update since the Cursor is already ready from the AsyncTask?
EDIT
I have changed my Adapter to avoid using CursorAdapter as #cricket_007 pointed out having an Adapter within an Adapter is bad design.
This is my Adapter:
public class SearchListAdapter extends RecyclerView.Adapter<SearchListAdapter.ViewHolder> {
private Context mContext;
private Cursor mCursor;
private boolean mDataValid;
private int mRowIdColumn;
private DataSetObserver mDataSetObserver;
public SearchListAdapter(Context context, Cursor c) {
mContext = context;
mCursor=c;
mDataValid = c != null;
mRowIdColumn = mDataValid ? mCursor.getColumnIndex("_id") : -1;
mDataSetObserver = new NotifyingDataSetObserver();
if (mCursor != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView itemTV;
ViewHolder(View itemView) {
super(itemView);
itemTV = (TextView) itemView.findViewById(R.id.itemTV);
}
}
#Override
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(true);
}
#Override
public int getItemCount() {
if (mDataValid && mCursor != null) {
return mCursor.getCount();
}
return 0;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// Passing the binding operation to cursor loader
mCursor.moveToPosition(position);
String town = mCursor.getString(mCursor.getColumnIndex(Constants.COLUMN_TOWN));
String zipcode = mCursor.getString(mCursor.getColumnIndex(Constants.COLUMN_ZIPCODE));
String zipcode_etx = mCursor.getString(mCursor.getColumnIndex(Constants.COLUMN_ZIPCODE_EXTENSION));
holder.itemTV.setText(zipcode+"-"+zipcode_etx+", "+town);
}
#Override
public SearchListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_search_list_item,parent,false);
// Passing the inflater job to the cursor-adapter
return new SearchListAdapter.ViewHolder(itemView);
}
public void swapCursor(Cursor cursor) {
Cursor old = changeCursor(cursor);
if (old != null) {
old.close();
}
}
private Cursor changeCursor(Cursor newCursor) {
if (newCursor == mCursor) {
return null;
}
final Cursor oldCursor = mCursor;
if (oldCursor != null && mDataSetObserver != null) {
oldCursor.unregisterDataSetObserver(mDataSetObserver);
}
mCursor = newCursor;
if (mCursor != null) {
if (mDataSetObserver != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
mRowIdColumn = newCursor.getColumnIndexOrThrow("_id");
mDataValid = true;
notifyDataSetChanged();
} else {
mRowIdColumn = -1;
mDataValid = false;
notifyDataSetChanged();
}
return oldCursor;
}
private class NotifyingDataSetObserver extends DataSetObserver {
#Override
public void onChanged() {
super.onChanged();
mDataValid = true;
notifyDataSetChanged();
}
#Override
public void onInvalidated() {
super.onInvalidated();
mDataValid = false;
notifyDataSetChanged();
}
}
}
Well, I found out why this was happening and the reason is weird. The problem has nothing to do with the RecyclerView but with the way the data is fetch.
In my AsyncTask, where I fetch the data, I wrote a Log.d to print the Cursor size like so:
#Override
protected Void doInBackground(Void... Void) {
if(type==Constants.GET_ZIP_CODES)
cursor = db.getAllZipCodes();
else
cursor = db.searchZipCodes(text);
Log.d("DATABASE","SIZE "+cursor.getCount());
return null;
}
This made the AsyncTask take longer, the ProgressDialog takes longer to go off. What I understand is that somehow, the database query is performed, the code keeps compiling, but the data is only ready in the Cursor after a while. Once I printed the result just after the query, it didn't go past the line until the cursor was fully loaded.
Actually this is not an answer(Would have put it in comment if i had enough reputation points) just a suggestion/case study i came across while loading data from database to recyclerView. Instead of directly sending the cursor over to adapter i sent it as an arraylist, but thats besides the point.
The place where i got the freeze like you seem to get is when i have to load a text with over 700-800 character into a card. So when i crop the text to less than 600 the freeze vanishes.
So just check if you have any data with large character set, if so try removing that and test it out.
Hope it works for you, suggestion put read more option for large text like whatsapp does!

Item in the first ListView repeats itself in Cursor loader

Please I am loading data from my local database and downloading image from my server into a ListView using a Cursor loader.
Everything works well, but the problem is that the images replaces each other incorrectly and later (in few seconds) returns to the normal(correct) order.
For instance if the ListView has four items (item A, B, C and D) with images, at on create the images changes, thus image of item A will replace image of item D and image of item B will replace the image of item C.
Below is what I have tried.
public class Notices extends NavigationDrawer implements LoaderCallbacks<Cursor> {
DotCursorAdapter mAdapter;
private ListView lv;
Context context = this;
private final int LOADER_ID = 1890;
public static DatabaseHandler dbHelper;
public String msgImageUrl;
public String senderImageUrl;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLayoutInflater().inflate(R.layout.list_main_activity, frameLayout);
lv = (ListView) findViewById(R.id.lists);
dbHelper = new DatabaseHandler(this);
mAdapter = new DotCursorAdapter(this, null,1);
getSupportLoaderManager().initLoader(LOADER_ID, null, this);
lv.setAdapter(mAdapter);
String mTitle = "Notices";
getSupportActionBar().setTitle(mTitle);
msgImageUrl = "http://akobima.newsysstm.com/images/";
}
#Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new DumbLoader(this);
}
#Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
mAdapter.swapCursor(cursor);
}
#Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
mAdapter.swapCursor(null);
}
/**
* DumbLoader sub class
*/
public static class DumbLoader extends CursorLoader {
public DumbLoader(Context context) {
super(context);
}
#Override
public Cursor loadInBackground() {
Cursor c = dbHelper.fetchAllNotices();
return c;
}
}
public final class DotCursorAdapter extends CursorAdapter {
public DotCursorAdapter(Context context, Cursor cursor, int flags) {
super(context, cursor, 0);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.notices, parent, false);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
//This downloads the msg image
if(msgImageName == null || msgImageName.length() == 0|| msgImageName.equalsIgnoreCase("null")){
msgImageView.getLayoutParams().height = 0;
msgImageView.requestLayout();
}else{
//instantiate the the downlaod image task
new DownloadImageTask(msgImageView).execute(msgImage);
msgImageView.getLayoutParams().height = 170;
msgImageView.requestLayout();
}
}
}
/**
* This sub class downloads the attached message image
* file to be viewed on the page.
*/
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
ImageView bmImage;
public DownloadImageTask(ImageView bmImage) {
this.bmImage = bmImage;
}
protected Bitmap doInBackground(String... urls) {
String urldisplay = urls[0];
Bitmap mIcon11 = null;
try {
InputStream in = new java.net.URL(urldisplay).openStream();
mIcon11 = BitmapFactory.decodeStream(in);
} catch (Exception e) {
Log.e("Error", e.getMessage());
e.printStackTrace();
}
return mIcon11;
}
protected void onPostExecute(Bitmap result) {
bmImage.setImageBitmap(result);
}
}
}
/**
* This is the xml list view
*/
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="#+id/lists"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fontFamily="sans-serif-condensed">
</ListView>
</RelativeLayout>
Please is there something I am doing wrong? Thanks for helping.
UPDATE
Please is it a bug for using LoaderCallbacks because i have tried all means possible and it is still not working. Please I would wish if somebody cold give me the confirmation that it is a bug or otherwise. Thank you in advance.
Try changing
mAdapter.swapCursor(data)
to
mAdapter.changeCursor(cursor)
and
mAdapter.swapCursor(null)
to
mAdapter.changeCursor(null)

Android database created list, how use in ListFragment

well I am using my existing database, I made a list<> "mSorular" with this code:
public class TestAdapter {
private final Context mContext;
private SQLiteDatabase mDb;
private DataBaseHelper mDbHelper;
public TestAdapter(Context context) {
this.mContext = context;
mDbHelper = new DataBaseHelper(mContext);
}
public List<soru> getTestData() {
List<soru> mSorular = new LinkedList<soru>();
try {
Cursor mCur = mDb.query("Soru", null, null,
null,
null,
null,
null,
null);
if (mCur != null) {
if (mCur.moveToFirst()) {
do {
soru Soru = new soru();
Soru.setmSoru(mCur.getString(2));
Soru.setmCevap1(mCur.getString(3));
Soru.setmCevap2(mCur.getString(4));
Soru.setmCevap3(mCur.getString(5));
mSorular.add(Soru);
} while (mCur.moveToNext());
}
}
return mSorular;
} catch (SQLException mSQLException) {
Log.e(TAG, "getTestData >>" + mSQLException.toString());
throw mSQLException;
}
}
after that I want to use my mSorular list in a listfragment. When I try to
public class framelist extends ListFragment {
private static String tag = "sqllist";
TestAdapter adapter = new TestAdapter(this);
it gives this error:
The constructor TestAdapter(framelist) is undefined
without this my list will return empty. so how can I Use my list in this listfragment
ListFragment is not a Context. First change it to use the attached Activity as Context, and then move it into the onCreate() method of the Fragment because before this call there might not be an Activity attached. (actually it's after the onAttach(Activity) call)
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate();
TestAdapter adapter = new TestAdapter(getActivity());
...
{

Populating a ListView using AsyncTaskLoader

I'm having problems using AsyncTaskLoader. This is my first attempt populating a ListView from a SQLite database using a loader.
Everything seems ok, when I rotate the screen the data is cached and no query is done again. But when I press the home button and launch my app again, the data is loaded again.
Note: Usuario means User, so I'm populating the ListView with a list of users.
public class Main extends SherlockFragmentActivity
implements LoaderManager.LoaderCallbacks<ArrayList<Usuario>> {
UsuarioAdapter adapter;
ListView listView;
Database db;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
listView = (ListView) findViewById(R.id.lista);
db = new Database(this);
adapter = new UsuarioAdapter(this, new ArrayList<Usuario>());
listView.setAdapter(adapter);
getSupportLoaderManager().initLoader(0, null, this);
}
#Override
public Loader<ArrayList<Usuario>> onCreateLoader(int id, Bundle args) {
return new UsuariosLoader(this, db);
}
#Override
public void onLoadFinished(Loader<ArrayList<Usuario>> loader,
ArrayList<Usuario> usuarios) {
//adapter.notifyDataSetChanged();
listView.setAdapter(new UsuarioAdapter(this, usuarios));
// ((BaseAdapter) listView.getAdapter()).notifyDataSetChanged();
}
#Override
public void onLoaderReset(Loader<ArrayList<Usuario>> loader) {
listView.setAdapter(null);
}
}
// THE LOADER
class UsuariosLoader extends AsyncTaskLoader<ArrayList<Usuario>> {
private ArrayList<Usuario> usuarios;
private Database db;
public UsuariosLoader(Context context, Database db) {
super(context);
this.db = db;
}
#Override
protected void onStartLoading() {
if (usuarios != null) {
deliverResult(usuarios); // Use the cache
}
forceLoad();
}
#Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
}
#Override
public ArrayList<Usuario> loadInBackground() {
db.open(); // Query the database
ArrayList<Usuario> usuarios = db.getUsuarios();
db.close();
return usuarios;
}
#Override
public void deliverResult(ArrayList<Usuario> data) {
usuarios = data; // Caching
super.deliverResult(data);
}
#Override
protected void onReset() {
super.onReset();
// Stop the loader if it is currently running
onStopLoading();
// Get rid of our cache if it exists
usuarios = null;
}
#Override
public void onCanceled(ArrayList<Usuario> data) {
// Attempt to cancel the current async load
super.onCanceled(data);
usuarios = null;
}
}
And I think this snippet is not well done. I'm creating a new Adapter instead of updating the data.
#Override
public void onLoadFinished(Loader<ArrayList<Usuario>> loader,
ArrayList<Usuario> usuarios) {
//adapter.notifyDataSetChanged();
listView.setAdapter(new UsuarioAdapter(this, usuarios));
//((BaseAdapter) listView.getAdapter()).notifyDataSetChanged();
}
Why adapter.notifyDataSetChanged() does not work?
So, basically, my app does not crash but all my data is reloaded again every time I restart the app.
Edit: This is my Adapter code:
class UsuarioAdapter extends BaseAdapter {
private ArrayList<Usuario> usuarios;
private LayoutInflater inflater;
public UsuarioAdapter(Context context, ArrayList<Usuario> usuarios) {
this.usuarios = usuarios;
this.inflater = LayoutInflater.from(context);
}
#Override
public int getCount() { return usuarios.size(); }
#Override
public Object getItem(int pos) { return usuarios.get(pos); }
#Override
public long getItemId(int pos) { return pos; }
#Override
public View getView(int pos, View convertView, ViewGroup arg) {
LinearLayout itemView;
if (convertView == null) {
itemView = (LinearLayout) inflater.inflate(R.layout.list_item, null);
} else {
itemView = (LinearLayout) convertView;
}
ImageView avatar = (ImageView) itemView.findViewById(R.id.avatar);
TextView nombre = (TextView) itemView.findViewById(R.id.nombre);
TextView edad = (TextView)itemView.findViewById(R.id.edad);
// Set the image ... TODO
nombre.setText(usuarios.get(pos).getNombre());
edad.setText(String.valueOf(usuarios.get(pos).getEdad()));
return itemView;
}
}
The call to notifyDataSetChanged() won't change the data your adapter is using. You need to update the data the adapter has, then call that method.
NotifyDataSetChanged() will only tell the adapter it needs to create it's views, but it does not change the data. You need to handle that yourself.
In your adapter add:
public void setUsuario(List<Usuario> usuarios) {
this.usuarios = usuarios;
}
Then in onLoadFinished() call the new method, then notifyDataSetChanged().
listView.getAdapter().setUsuario(usuarios);
listView.getAdapter().notifiyDataSetChanged();
I've found the solution. The onStartLoading was the guilty:
#Override
protected void onStartLoading() {
if (usuarios != null) {
deliverResult(usuarios); // Use cache
} else {
forceLoad();
}
}
In my original post forceLoad was always called. It must be in the else branch.

Unable to make AlphabetIndexer work

I'm attempting to implement the AlphabetIndexer to help the users scroll through my list, but nothing shows up on the list when I run the app. Could someone please tell me why?
Note: I am not instantiating an AlphabetIndexer in the Adapter's constructor because, at that point, no Cursor is available.
Here is the relevant code:
In the Activity's onCreate() method:
mList = (ListView)findViewById(R.id.mylist);
mList.setOnItemClickListener(this);
mList.setFastScrollEnabled(true);
mAdapter = new MyAdapter(MyActivity.this, R.layout.layout_list_row, null, new String[] {MyColumns.NAME}, new int[] {R.id.itemname});
mList.setAdapter(mAdapter);
mList.setFastScrollEnabled(true);
doQuery();
doQuery() is a method that queries for a Cursor using an AsyncQueryHandler. The AsyncQueryHandler looks like this:
private final class MyQueryHandler extends AsyncQueryHandler {
public MyQueryHandler(Context context) {
super(context.getContentResolver());
}
#Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
if (!isFinishing()) {
if (mAdapter != null) {
mAdapter.changeCursor(cursor);
}
}
else {
cursor.close();
}
}
}
Lastly, my SimpleCursorAdapter. I've taken out the unnecessary parts:
public class MyAdapter extends SimpleCursorAdapter implements View.OnClickListener {
private Cursor mCursor;
AlphabetIndexer alphaIndexer;
public MyAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c, from, to);
}
public int getPositionForSection(int section) {
return alphaIndexer.getPositionForSection(section);
}
public int getSectionForPosition(int position) {
return alphaIndexer.getSectionForPosition(position);
}
public Object[] getSections() {
return alphaIndexer.getSections();
}
public void onClick(View v) {
// ...
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
// ...
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
// ...
}
#Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
if (MyActivity.this.mCursor != null) {
stopManagingCursor(MyActivity.this.mCursor);
MyActivity.this.mCursor.close();
MyActivity.this.mCursor = null;
mCursor = null;
}
MyActivity.this.mCursor = cursor;
startManagingCursor(MyActivity.this.mCursor);
mCursor = cursor;
alphaIndexer = new AlphabetIndexer(mCursor, mCursor.getColumnIndex(MyColumns.NAME), " ABCDEFGHIJKLMNOPQRSTUVWXYZ");
alphaIndexer.setCursor(mCursor);
}
#Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
return doQuery();
}
}
Sometimes Android will hide the fast-scroll functionality if your list isn't long enough to warrant fast-scrolling. Not sure if that's your problem, but it might be worth trying to add a bunch of items to the list.
I've just lost couple of hours on alphabet indexer and fast scroller. In my case the list wasn't always long enough to warant the fast scroll/alphabet indexer feature. The exact behavior can be found in class FastScroller which is a helper class for AbsListView. There is a piece of code there that decides if "the list is long"
final boolean longList = childCount > 0 && itemCount / childCount >= MIN_PAGES;
MIN_PAGES is defined with value of 4. There you have it, if your list item count is not at least 4x the child count (visible rows) fast scroller and thus alphabet indexer will not appear.

Categories

Resources