Question

I'm trying to use CursorLoader with a CursorTreeAdapter but am running into an issue I can't figure out. (If you have a working example of this, feel free to skip the rest and attach it below. I would be very thankful) The first time I open a group, everything works fine. If I then close and re-open the group, I get an overflow error. Here is the error:

V/SpellBook(29520): Activity being created 
D/SpellBook(29520): onCreateLoader for id 123456 
V/SpellBook(29520): Resuming 
V/SpellBook(29520): Processing query for uri content://com.zalzala.spellbook.SpellProvider/levels/bard 
D/SpellBook(29520): onLoadFinished() for id 123456 
D/SpellBook(29520): onCreateLoader for id 3 
V/SpellBook(29520): Processing query for uri content://com.zalzala.spellbook.SpellProvider/class/bard/3 
D/SpellBook(29520): onLoadFinished() for id 3 

Everything is fine here. So far I've opened the activity and opened group number 3. Here is what happens if I close group 3 and open it again:

D/SpellBook(29520): onLoadFinished() for id 3 
D/SpellBook(29520): onLoadFinished() for id 3 
D/SpellBook(29520): onLoadFinished() for id 3 
D/SpellBook(29520): onLoadFinished() for id 3 
D/SpellBook(29520): onLoadFinished() for id 3 
(... and a lot more of these) 
    E/AndroidRuntime(29520): FATAL EXCEPTION: main 
E/AndroidRuntime(29520): java.lang.StackOverflowError 
E/AndroidRuntime(29520):    at java.util.HashMap.get(HashMap.java:302) 
E/AndroidRuntime(29520):    at android.database.sqlite.SQLiteCursor.getColumnIndex(SQLiteCursor.java:355)
E/AndroidRuntime(29520):    at android.database.CursorWrapper.getColumnIndex(CursorWrapper.java:67) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment$MyExpandableListAdapter.getChildrenCursor(SpellListView.java:216) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.setChildrenCursor(CursorTreeAdapter.java:159) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:183) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:1) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:413) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:547) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment$MyExpandableListAdapter.getChildrenCursor(SpellListView.java:217) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.setChildrenCursor(CursorTreeAdapter.java:159) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:183) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:1) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:413) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:547) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment$MyExpandableListAdapter.getChildrenCursor(SpellListView.java:217) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.setChildrenCursor(CursorTreeAdapter.java:159) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:183) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:1) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:413) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:547) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment$MyExpandableListAdapter.getChildrenCursor(SpellListView.java:217) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.setChildrenCursor(CursorTreeAdapter.java:159) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:183) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:1) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:413) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:547) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment$MyExpandableListAdapter.getChildrenCursor(SpellListView.java:217) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.java:106) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.setChildrenCursor(CursorTreeAdapter.java:159) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:183) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment.onLoadFinished(SpellListView.java:1) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:413) 
E/AndroidRuntime(29520):    at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:547) 
E/AndroidRuntime(29520):    at com.zalzala.spellbook.SpellListView$ExpandableListCursorLoaderFragment$MyExpandableListAdapter.getChildrenCursor(SpellListView.java:217) 
E/AndroidRuntime(29520):    at android.widget.CursorTreeAdapter.getChildrenCursorHelper(CursorTreeAdapter.ja 
W/ActivityManager( 6887):   Force finishing activity com.zalzala.spellbook/.SpellListView

So for some reason onLoadFinished() gets called over and over for the child cursor when re-opening a group.

Here is my loader implementation:

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // TODO Auto-generated method stub
        Log.d(Spellbook.TAG,"onCreateLoader for id "+id);
        if (id <123456){
            Uri spellUri = Uri.withAppendedPath(SpellProvider.CONTENT_URI, "class");
            spellUri = Uri.withAppendedPath(spellUri, mCLASS);
            spellUri = ContentUris.withAppendedId(spellUri, id);
            return new CursorLoader(getActivity(), spellUri, null, null, null, null);
        }else {
            //get group cursor
            Uri groupUri = Uri.withAppendedPath(SpellProvider.CONTENT_URI, "levels");
            groupUri = Uri.withAppendedPath(groupUri, mCLASS);
            return new CursorLoader(getActivity(), groupUri, null, null, null, null);
        }
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // TODO Auto-generated method stub
        int id = loader.getId();
        Log.d(Spellbook.TAG,"onLoadFinished() for id "+id);
        if (id < 123456){
            //child cursor
            ((CursorTreeAdapter) mAdapter).setChildrenCursor(id, data);
        } else {
            ((CursorTreeAdapter) mAdapter).setGroupCursor(data);
        }

    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        // TODO Auto-generated method stub
        int id = loader.getId();
        Log.d(Spellbook.TAG,"onLoaderReset() for id "+id);
        if (id < 123456){
            //child cursor
            ((CursorTreeAdapter) mAdapter).setChildrenCursor(id, null);
        } else {
            ((CursorTreeAdapter) mAdapter).setGroupCursor(null);
        }

    }

I call CursorTreeAdapter with the constructor which specifies auto requery as false.

    public class MyExpandableListAdapter extends CursorTreeAdapter {

        public MyExpandableListAdapter(Cursor cursor, Context context) {
            super(cursor, context, false); //do not auto requery. pretty sure CursorLoader needs this.
        }

        @Override
        protected Cursor getChildrenCursor(Cursor groupCursor) {
            // Given the group, we return a cursor for all the children within that group 
            int id = groupCursor.getInt(groupCursor.getColumnIndex(SpellDbAdapter.KEY_LEVEL));
            getLoaderManager().initLoader(id, null, ExpandableListCursorLoaderFragment.this);
            return null;
        }

Thanks for any help!

Was it helpful?

Solution

call destroyLoader() on onGroupCollapsed(). But it doesn't solve problem completely. Looking on CursorTreeAdapter realization make me sure to not use it with CursorLoader.

It's really better to extend BaseExpandableAdapter. This way allow to avoid unnecessary (in case of using loaders) usage of content observers. In my custom adapter i'm keep created loaders in sparse array and destroy them on group collapse. Works well :)

OTHER TIPS

This is an old question but there's still no correct answer (well now there is).

The MyExpandableListAdapter has a getChildrenCursor(Cursor) method:

@Override
protected Cursor getChildrenCursor(Cursor groupCursor) {
    int id = groupCursor.getInt(groupCursor.getColumnIndex(SpellDbAdapter.KEY_LEVEL));
    getLoaderManager().initLoader(id, null, ExpandableListCursorLoaderFragment.this);
    return null;
}

The method starts the Loader with the initLoader call. Once the data is loaded, onLoadFinished will be called and the children cursor is set:

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // some code
    ((CursorTreeAdapter) mAdapter).setChildrenCursor(id, data);
    // some more code
}

The CursorTreeAdapter.setChildrenCursor method goes like this:

public void setChildrenCursor(int groupPosition, Cursor childrenCursor) {
    MyCursorHelper childrenCursorHelper = getChildrenCursorHelper(groupPosition);
    childrenCursorHelper.changeCursor(childrenCursor, false);
}

It retrieves the childrenCursorHelper with a call to CursorTreeAdapter.getChildrenCursorHelper:

synchronized MyCursorHelper getChildrenCursorHelper(int groupPosition, boolean requestCursor) {
    MyCursorHelper cursorHelper = mChildrenCursorHelpers.get(groupPosition);

    if (cursorHelper == null) {
        if (mGroupCursorHelper.moveTo(groupPosition) == null) return null;

        final Cursor cursor = getChildrenCursor(mGroupCursorHelper.getCursor());
        cursorHelper = new MyCursorHelper(cursor);
        mChildrenCursorHelpers.put(groupPosition, cursorHelper);
    }

    return cursorHelper;
}

Under certain circumstances the childrenCursorHelper doesn't exist yet and the method now calls getChildrenCursor(Cursor) which is the method that started the whole call chain. Logically we will run into a StackOverflowError.

This is a bug in the CursorTreeAdapter class. While others recommend to use the BaseExpandableAdapter instead, I recommend to create a custom CursorTreeAdapter class and make the necessary changes and fixes.

The StackOverflowError can easily be fixed by changing the getChildrenCursorHelper method. Create the MyCursorHelper first, add it to mChildrenCursorHelpers and then retrieve the children cursor if retrieveChildCursor is set to true (new parameter). Calling the method from setChildrenCursor means we pass false to not get into the endless recursion.

synchronized MyCursorHelper getChildrenCursorHelper(int groupPosition, boolean retrieveChildCursor) {
    MyCursorHelper cursorHelper = mChildrenCursorHelpers.get(groupPosition);

    if (cursorHelper == null && mGroupCursorHelper.moveTo(groupPosition) != null) {
        cursorHelper = new MyCursorHelper(null);
        mChildrenCursorHelpers.put(groupPosition, cursorHelper);

        if (retrieveChildCursor) {
            final Cursor cursor = getChildrenCursor(mGroupCursorHelper.getCursor());
            if (cursor != null) {
                cursorHelper.changeCursor(cursor, false);
            }
        }
    }

    return cursorHelper;
}

Also remove the code in the MyCursorHelper.deactivate method since the CursorLoader takes care of closing obsolete cursors.

Ok, so talking to someone at Google, Expanded Lists should probably have been deprecated a long time ago. If you really need to use cursor loaders and expanded lists, extend the base adapter. If you don't really need it, go ahead and not use cursor loader.

Feel free to correct me :)

I think when we use initLoader, it will make stack full, try using restartLoader instead.

@Override
    protected Cursor getChildrenCursor(Cursor groupCursor) {
        // TODO Auto-generated method stub          
        Bundle b = new Bundle();
        //b.putString("Artist", "\""+groupCursor.getString(groupCursor.getColumnIndex(artist.ARTIST))+"\"");
        b.putString("Artist", groupCursor.getString(groupCursor.getColumnIndex(artist.ARTIST)));
        getLoaderManager().restartLoader(groupCursor.getPosition(), b, MpArtist.this);
        return null;
    }

Try this:

@Override
public void onLoadFinished(Loader<Cursor> loader, final Cursor data) {
    // ...
    final int id = loader.getId();
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            ((CursorTreeAdapter) mAdapter).setChildrenCursor(id, data);
        }
    });
 }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top