Android ListView with SimpleCursorAdapter - CursorIndexOutOfBoundsException error

假装没事ソ 提交于 2019-12-08 12:46:02

问题


I have a ListView in AcitivityA that is populated using a custom SimpleCursorAdapter called RecipeAdapter. The adapter holds data from SQLite

There is a EditText view at the top of the ListView, that filters the listview as the user searches for a recipe. When a user clicks on a item in the filtered ListView, ActivityB starts.

This all works perfectly. However when the user presses the backbutton to resume ActivityB, I get the following error.

java.lang.RuntimeException: Unable to resume activity {ttj.android.quorn/ttj.android.quorn.RecipeActivity}: 
java.lang.IllegalStateException: trying to requery an already closed cursor  android.database.sqlite.SQLiteCursor@418ae5d8

To fix this problem, I modified the onResume() from:

...
c = db.getCursor(); 
adapter.changeCursor(c);

to

....
Cursor cursor = db.getCursor(); 
adapter.changeCursor(cursor);

I then get the following exception. In the Logcat, the problem arises with the getId() method in DBHelper. I have added c.moveToFirst() in this method, but this still doesn't solve the problem.

FATAL EXCEPTION: main
android.database.CursorIndexOutOfBoundsException: Index -1 requested, with a size of 70
at android.database.AbstractCursor.checkPosition(AbstractCursor.java:400)
at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:136)
at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:50)
at ttj.android.quorn.DBHelper.getId(DBHelper.java:224)
at ttj.android.quorn.RecipeActivity$RecipeHolder.populateFrom(RecipeActivity.java:650)
at ttj.android.quorn.RecipeActivity$RecipeAdapter.bindView(RecipeActivity.java:572)
at android.support.v4.widget.CursorAdapter.getView(CursorAdapter.java:256)
at android.widget.AbsListView.obtainView(AbsListView.java:2214)
at android.widget.ListView.makeAndAddView(ListView.java:1774)
at android.widget.ListView.fillDown(ListView.java:672)
at android.widget.ListView.fillFromTop(ListView.java:732)
at android.widget.ListView.layoutChildren(ListView.java:1611)
at android.widget.AbsListView.onLayout(AbsListView.java:2044)
at android.view.View.layout(View.java:11418)
at android.view.ViewGroup.layout(ViewGroup.java:4224)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1628)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1486)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1399)
at android.view.View.layout(View.java:11418)
at android.view.ViewGroup.layout(ViewGroup.java:4224)
at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
at android.view.View.layout(View.java:11418)
at android.view.ViewGroup.layout(ViewGroup.java:4224)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1628)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1486)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1399)
at android.view.View.layout(View.java:11418)
at android.view.ViewGroup.layout(ViewGroup.java:4224)
at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
at android.view.View.layout(View.java:11418)
at android.view.ViewGroup.layout(ViewGroup.java:4224)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1628)
at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2585)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4507)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
at dalvik.system.NativeStart.main(Native Method)

Can anyone help me with my problem?

Here is my code:

In the onCreate, the cursor populate the ListView using c.getCursor and when the user filters the ListView via the EditText, the c.getFilterCursor is used.

public class RecipeActivity extends SherlockListActivity {

private DBHelper db = null;
private Cursor c = null;
private RecipeAdapter adapter = null;
ListView listContent;   
private EditText filterText = null;

@SuppressWarnings("deprecation")
@Override
public void onCreate(Bundle savedInstanceState) {
    try {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.filter_list);

        filterText = (EditText) findViewById(R.id.search_box);
        filterText.addTextChangedListener(filterTextWatcher);

        ListView listContent = getListView();

        db = new DBHelper(this);
        db.createDataBase();
        db.openDataBase();

        c = db.getCursor();         

        adapter = new RecipeAdapter(c);

        listContent.setAdapter(adapter);

        adapter.setFilterQueryProvider(new FilterQueryProvider() {
            public Cursor runQuery(CharSequence constraint) {
                // Search for states whose names begin with the specified letters.
                c = db.getFilterCursor(constraint);
                return c;
            }
        });

        startManagingCursor(c);


    } catch (IOException e) {
        e.printStackTrace();
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    filterText.removeTextChangedListener(filterTextWatcher);
    db.close();
}


@SuppressWarnings("deprecation")
@Override
protected void onResume() {
    super.onResume();

    Cursor cursor = db.getCursor(); 
    adapter.changeCursor(cursor);

}


@Override
protected void onPause() {
    super.onPause();

    adapter.notifyDataSetInvalidated();
    adapter.changeCursor(null);

}




    private TextWatcher filterTextWatcher = new TextWatcher() {

    public void afterTextChanged(Editable s) {
    }

    public void beforeTextChanged(CharSequence s, int start, int count,
            int after) {
    }

    public void onTextChanged(CharSequence s, int start, int before,
            int count) {

        adapter.getFilter().filter(s);


    }

};

RecipeAdapter inner class

class RecipeAdapter extends CursorAdapter {

    @SuppressWarnings("deprecation")
    public RecipeAdapter(Cursor c) {
        super(RecipeActivity.this, c);
    }

    public void bindView(View row, Context arg1, Cursor arg2) {
        RecipeHolder holder = (RecipeHolder) row.getTag();
        holder.populateFrom(c, db);

    }

    public View newView(Context arg0, Cursor arg1, ViewGroup arg2) {
        LayoutInflater inflater = getLayoutInflater();
        View row = inflater.inflate(R.layout.reciperow, arg2, false);
        RecipeHolder holder = new RecipeHolder(row);
        row.setTag(holder);

        return (row);
    }


static class RecipeHolder {
    public TextView id = null;
    private TextView name = null;
    private TextView desc = null;
    private TextView preptime = null;
    private TextView cooktime = null;
    private TextView serves = null;
    private TextView calories = null;
    private TextView fat = null;
    private TextView fav = null;

    RecipeHolder(View row) {
        id = (TextView) row.findViewById(R.id.id);
        name = (TextView) row.findViewById(R.id.recipe);
        desc = (TextView) row.findViewById(R.id.desc);
        preptime = (TextView) row.findViewById(R.id.preptime);
        cooktime = (TextView) row.findViewById(R.id.cooktime);
        serves = (TextView) row.findViewById(R.id.serving);
        calories = (TextView) row.findViewById(R.id.calories);
        fat = (TextView) row.findViewById(R.id.fat);
        fav = (TextView) row.findViewById(R.id.fav);
    }


    void populateFrom(Cursor c, DBHelper r) {
        id.setText(r.getId(c));
        name.setText(r.getRecipe(c));
        name.setTextColor(Color.parseColor("#CCf27c22"));
        desc.setText(r.getDesc(c));
        preptime.setText(r.getPrepTime(c) + ". ");
        cooktime.setText(r.getCookTime(c) + " mins");
        serves.setText(r.getServes(c));
        calories.setText(r.getCalories(c));
        fat.setText(r.getFat(c));
        fav.setText(r.getFav(c));

DBHelper class

public Cursor getCursor() {
    SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
    queryBuilder.setTables(DATABASE_TABLE);

    String[] columns = new String[] { KEY_ROWID, RECIPE, DESC, PREPTIME,
            COOKTIME, SERVES, CALORIES, FAT, CATEGORY, FAV };

    Cursor myCursor = queryBuilder.query(myDataBase, columns, null, null,
            null, null, RECIPE + " ASC");

    return myCursor;
}


public Cursor getFilterCursor(CharSequence constraint) {
    SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
    queryBuilder.setTables(DATABASE_TABLE);

    String[] columns = new String[] { KEY_ROWID, RECIPE, DESC, PREPTIME,
            COOKTIME, SERVES, CALORIES, FAT, CATEGORY, FAV };

    if (constraint == null || constraint.length() == 0) {
        // Return the full list
        return queryBuilder.query(myDataBase, columns, null, null, null,
                null, RECIPE + " ASC");
    } else {
        String value = "%" + constraint.toString() + "%";

        return myDataBase.query(DATABASE_TABLE, columns, "RECIPE like ? ",
                new String[] { value }, null, null, null);
    }
}

public String getId(Cursor c) {
               c.moveToFirst();
    return (c.getString(0));
}

public String getRecipe(Cursor c) {
    return (c.getString(1));
}

public String getDesc(Cursor c) {
    return (c.getString(2));
}

public String getPrepTime(Cursor c) {
    return (c.getString(3));
}

public String getCookTime(Cursor c) {
    return (c.getString(4));
}

public String getServes(Cursor c) {
    return (c.getString(5));
}

public String getCalories(Cursor c) {
    return (c.getString(6));
}

public String getFat(Cursor c) {
    return (c.getString(7));
}

public String getCategory(Cursor c) {
    return (c.getString(8));
}

public String getFav(Cursor c) {
    return (c.getString(9));
}

回答1:


@SuppressWarnings("deprecation")

Bad. You should get rid of the deprecation instead of hiding that :)

startManagingCursor(c);

Don't do that. That may have caused the requery on the already closed cursor. Simply remove that line.

    adapter.setFilterQueryProvider(new FilterQueryProvider() {
        public Cursor runQuery(CharSequence constraint) {
            // Search for states whose names begin with the specified letters.
            c = db.getFilterCursor(constraint);
            return c;
        }
    });

Don't overwrite your c here. Just return db.getFilterCursor(constraint); is what this should do. Other things that may have a positive effect

@SuppressWarnings("deprecation")
public RecipeAdapter(Cursor c) {
    super(RecipeActivity.this, c);
}

public RecipeAdapter(Cursor c) {
    // no requeries and no observer required if you change the cursor yourself
    super(RecipeActivity.this, c, 0)
}

Next one:

adapter.notifyDataSetInvalidated();
adapter.changeCursor(null);

// change to
adapter.changeCursor(null);
adapter.notifyDataSetChanged(); // maybe without this

As far as I understand the documentation notifyDataSetInvalidated() means that the data can't be valid afterwards ("Once invoked this adapter is no longer valid and should not report further data set changes.") and you need to create a new Adapter instance. Not sure though. Just doing notifyDataSetChanged() works fine. It might even be the case that doing adapter.changeCursor() will already implicitly do the change notification.

P.S.: c.MoveToFirst() is not required. The CursorAdapter will move the cursor to the required position.




回答2:


You renamed your variable, as indicated here

....
Cursor cursor = db.getCursor(); 
adapter.changeCursor(cursor);

correct? But right after that you specify that you tried

c.moveToFirst()

So maybe you should set

c = cursor;

So that the rest of your code works?



来源:https://stackoverflow.com/questions/11909363/android-listview-with-simplecursoradapter-cursorindexoutofboundsexception-erro

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