NPE: CursorTreeAdapter$MyCursorHelper.changeCursor(Cursor, boolean)' on a null object reference

337 views Asked by At

I'm trying to get data from my database in different cursors and show it in an ExpandableListView separated by groups. Like the following:

  • History tasks (to cursor 0)
    • History 1
    • History 2
  • Geography tasks (to cursor 1)
    • Geography 1
    • Geography 2
  • Foreign language tasks (to cursor 2)
    • Foreign language 1
    • Foreign language 2

I'm trying to do it using CursorTreeAdapter, as I have all my info in a database and it can manage different cursors and show the info properly in an ExpandableListView.

The problem I'm getting is that I get a NPE at some point of my code but I'm not able to check where it is debugging, because it directly change to another file without knowing what is happening.

When I'm debugging in the CursorTreeAdapter::setChildrenCursor method, just when I step into

childrenCursorHelper.changeCursor(childrenCursor, false);

it fails, so I don't know what happens inside this method.

My code is the following:

MtMainActivity.java

public class MtMainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {

    @InjectView(R.id.elvTaskList)
    protected ExpandableListView elvTaskList;
    private TaskCursorAdapter taskListAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        taskListAdapter = new TaskCursorAdapter(null, this);
        elvTaskList.setAdapter(taskListAdapter);
        getSupportLoaderManager().initLoader(MtLoaders.HISTORY_TASK, null, this);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        switch (id) {
            case MtLoaders.HISTORY_TASK:
                return mtLoaderFactory.createHistoryTaskLoader();
        }
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        switch (loader.getId()) {
            case MtLoaders.HISTORY_TASK:
                taskListAdapter.setChildrenCursor(0, cursor);
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        switch (loader.getId()) {
            case MtLoaders.HISTORY_TASK:
                taskListAdapter.setChildrenCursor(0, null);
        }
    }

TaskCursorAdapter.java

public class TaskCursorAdapter extends CursorTreeAdapter {

    private final Cursor cursor;

    public TaskCursorAdapter(Cursor cursor, Context context) {
        super(cursor, context);
        this.cursor = cursor;
    }

    @Override
    protected Cursor getChildrenCursor(Cursor groupCursor) {
        return cursor;
    }

    @Override
    protected View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) {
        final LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        final TextView tvHeader = (TextView) layoutInflater.inflate(R.layout.mt_task_item_subtitle, parent, false);
        tvHeader.setText(">> Insert name here <<");
        return tvHeader;
    }

    @Override
    protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) { /* nothing */ }

    @Override
    protected View newChildView(Context context, Cursor cursor, boolean isLastChild, ViewGroup parent) {
        final LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        final View view = layoutInflater.inflate(R.layout.mt_task_item, parent, false);

        return view;
    }

    @Override
    protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) { /* nothing */ }
}

Error

E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: project.android, PID: 11592
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.CursorTreeAdapter$MyCursorHelper.changeCursor(android.database.Cursor, boolean)' on a null object reference
        at android.widget.CursorTreeAdapter.setChildrenCursor(CursorTreeAdapter.java:164)
        at project.android.ui.MtMainActivity.onLoadFinished(MtMainActivity.java:215)
        at project.android.ui.MtMainActivity.onLoadFinished(MtMainActivity.java:29)
        at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:427)
        at android.support.v4.app.LoaderManagerImpl$LoaderInfo.onLoadComplete(LoaderManager.java:395)
        at android.support.v4.content.Loader.deliverResult(Loader.java:104)
        at android.support.v4.content.AsyncTaskLoader.dispatchOnLoadComplete(AsyncTaskLoader.java:223)
        at android.support.v4.content.AsyncTaskLoader$LoadTask.onPostExecute(AsyncTaskLoader.java:61)
        at android.support.v4.content.ModernAsyncTask.finish(ModernAsyncTask.java:461)
        at android.support.v4.content.ModernAsyncTask.access$500(ModernAsyncTask.java:47)
        at android.support.v4.content.ModernAsyncTask$InternalHandler.handleMessage(ModernAsyncTask.java:474)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:135)
        at android.app.ActivityThread.main(ActivityThread.java:5221)
        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:899)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

PS: Cursor works correctly, I checked using a unique ListView and there was no problem with this.

2

There are 2 answers

0
Sash0k On

I have same issue in my project. Seems, it's a problem in CursorTreeAdapter:

at android.widget.CursorTreeAdapter.setChildrenCursor(CursorTreeAdapter.java:164)

Look at that method inside Android sources:

    public void setChildrenCursor(int groupPosition, Cursor childrenCursor) {

    /*
     * Don't request a cursor from the subclass, instead we will be setting
     * the cursor ourselves.
     */
    MyCursorHelper childrenCursorHelper = getChildrenCursorHelper(groupPosition, false);

    /*
     * Don't release any cursor since we know exactly what data is changing
     * (this cursor, which is still valid).
     */
    childrenCursorHelper.changeCursor(childrenCursor, false);
}

At some cases getChildrenCursorHelper returns null into childrenCursorHelper. I get it usually after configuration changes (e.g. screen rotation).

To solve this, I add CursorFilter + CursorTreeAdapter sources into my project, next add null check to childrenCursorHelper

    public void setChildrenCursor(int groupPosition, Cursor childrenCursor) {

    /*
     * Don't request a cursor from the subclass, instead we will be setting
     * the cursor ourselves.
     */
    MyCursorHelper childrenCursorHelper = getChildrenCursorHelper(groupPosition, false);

    /*
     * Don't release any cursor since we know exactly what data is changing
     * (this cursor, which is still valid).
     */
    if (childrenCursorHelper != null)
        childrenCursorHelper.changeCursor(childrenCursor, false);
}

and finally extend my adapter from fixed CursorTreeAdapter.
See this commit for details.

0
csname1910 On

You don't need to add CursorTreeAdapter sources to the project.

Just override setChildrenCursor:

public class MyAdapter extends CursorTreeAdapter {

    @Override
    public void setChildrenCursor(int groupPosition, Cursor childrenCursor) {
        try {
            Method getChildrenCursorHelper = CursorTreeAdapter.class.getDeclaredMethod("getChildrenCursorHelper", int.class, boolean.class);
            getChildrenCursorHelper.setAccessible(true);
            Object childrenCursorHelper = getChildrenCursorHelper.invoke(this, groupPosition, false);
            if (childrenCursorHelper != null) {
                Method changeCursor = childrenCursorHelper.getClass().getDeclaredMethod("changeCursor", Cursor.class, boolean.class);
                changeCursor.setAccessible(true);
                changeCursor.invoke(childrenCursorHelper, childrenCursor, false);
            }
        } catch (IllegalAccessException e) {
        } catch (InvocationTargetException e) {
        } catch (NoSuchMethodException e) {
        }
    }
}