Android: ListView with CheckBox populated from SQLite database not quite working

As with several other posts, I am trying to create a ListView that includes a CheckBox for each row, and use the SQLite database to store the current selection state.

Starting with the example in http://appfulcrum.com/?p=351 , which doesn’t quite work as it is, I created a simple application that creates a database, fills it contains 20 elements and displays a list.

It successfully retrieves the state and saves the selection state.

BUT, it does not correctly display the status of the CheckBox, if I change it, go to the other end of the list and scroll back. for example, if I select the first CheckBox, scroll down and return to the top, the CheckBox will no longer be installed. This is done on a Samsung Android 2.1 phone.

If I return to the main screen, return to the list, correctly install CheckBox , so the database is really updated.

This example extends SimpleCursorAdapter, and getView () calls setChecked () with true or false, respectively, based on the value of the select column in the table.

Below are all sources.

I would love to say: "Du, here's your problem ..."

CustomListViewDB.java

// src/CustomListViewDB.java package com.appfulcrum.blog.examples.listviewcustomdb; import android.app.ListActivity; import android.database.Cursor; import android.database.SQLException; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ListView; import android.widget.Toast; public class CustomListViewDB extends ListActivity { private ListView mainListView = null; CustomSqlCursorAdapter adapter = null; private SqlHelper dbHelper = null; private Cursor currentCursor = null; private ListView listView = null; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple); if (this.dbHelper == null) { this.dbHelper = new SqlHelper(this); } listView = getListView(); listView.setItemsCanFocus(false); listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); //listView.setClickable(true); Button btnClear = (Button) findViewById(R.id.btnClear); btnClear.setOnClickListener(new OnClickListener() { public void onClick(View v) { Toast.makeText(getApplicationContext(), " You clicked Clear button", Toast.LENGTH_SHORT).show(); ClearDBSelections(); } }); new SelectDataTask().execute(); this.mainListView = getListView(); mainListView.setCacheColorHint(0); } @Override protected void onRestart() { super.onRestart(); new SelectDataTask().execute(); } @Override protected void onPause() { super.onPause(); this.dbHelper.close(); } protected void ClearDBSelections() { this.adapter.ClearSelections(); } private class SelectDataTask extends AsyncTask<Void, Void, String> { protected String doInBackground(Void... params) { try { CustomListViewDB.this.dbHelper.createDatabase(dbHelper.dbSqlite); CustomListViewDB.this.dbHelper.openDataBase(); CustomListViewDB.this.currentCursor = CustomListViewDB.this.dbHelper .getCursor(); } catch (SQLException sqle) { throw sqle; } return null; } // can use UI thread here protected void onPostExecute(final String result) { startManagingCursor(CustomListViewDB.this.currentCursor); int[] listFields = new int[] { R.id.txtTitle }; String[] dbColumns = new String[] { SqlHelper.COLUMN_TITLE }; CustomListViewDB.this.adapter = new CustomSqlCursorAdapter( CustomListViewDB.this, R.layout.single_item, CustomListViewDB.this.currentCursor, dbColumns, listFields, CustomListViewDB.this.dbHelper); setListAdapter(CustomListViewDB.this.adapter); } } } 

CustomSqlCursorAdapter.java

 // src/CustomSqlCursorAdapter.java package com.appfulcrum.blog.examples.listviewcustomdb; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.SimpleCursorAdapter; import android.widget.TextView; public class CustomSqlCursorAdapter extends SimpleCursorAdapter { private Context mContext; private SqlHelper mDbHelper; private Cursor mCurrentCursor; public CustomSqlCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to, SqlHelper dbHelper) { super(context, layout, c, from, to); this.mCurrentCursor = c; this.mContext = context; this.mDbHelper = dbHelper; } public View getView(int pos, View inView, ViewGroup parent) { View v = inView; if (v == null) { LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = inflater.inflate(R.layout.single_item, null); } if (!this.mCurrentCursor.moveToPosition(pos)) { throw new SQLException("CustomSqlCursorAdapter.getView: Unable to move to position: "+pos); } CheckBox cBox = (CheckBox) v.findViewById(R.id.bcheck); // save the row _id value in the checkbox tag for retrieval later cBox.setTag(Integer.valueOf(this.mCurrentCursor.getInt(0))); if (this.mCurrentCursor.getInt(SqlHelper.COLUMN_SELECTED_idx) != 0) { cBox.setChecked(true); Log.w("SqlHelper", "CheckBox true for pos "+pos+", id="+this.mCurrentCursor.getInt(0)); } else { cBox.setChecked(false); Log.w("SqlHelper", "CheckBox false for pos "+pos+", id="+this.mCurrentCursor.getInt(0)); } //cBox.setOnClickListener(this); cBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { Log.w("SqlHelper", "Selected a CheckBox and in onCheckedChanged: "+isChecked); Integer _id = (Integer) buttonView.getTag(); ContentValues values = new ContentValues(); values.put(SqlHelper.COLUMN_SELECTED, isChecked ? Integer.valueOf(1) : Integer.valueOf(0)); mDbHelper.dbSqlite.beginTransaction(); try { if (mDbHelper.dbSqlite.update(SqlHelper.TABLE_NAME, values, "_id=?", new String[] { Integer.toString(_id) }) != 1) { throw new SQLException("onCheckedChanged failed to update _id="+_id); } mDbHelper.dbSqlite.setTransactionSuccessful(); } finally { mDbHelper.dbSqlite.endTransaction(); } Log.w("SqlHelper", "-- _id="+_id+", isChecked="+isChecked); } }); TextView txtTitle = (TextView) v.findViewById(R.id.txtTitle); txtTitle.setText(this.mCurrentCursor.getString(this.mCurrentCursor .getColumnIndex(SqlHelper.COLUMN_TITLE))); return (v); } public void ClearSelections() { this.mDbHelper.clearSelections(); this.mCurrentCursor.requery(); } } 

ListViewWithDBActivity.java

 package com.appfulcrum.blog.examples.listviewcustomdb; import android.app.Activity; import android.os.Bundle; public class ListViewWithDBActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } } 

SqlHelper

 // SqlHelper.java package com.appfulcrum.blog.examples.listviewcustomdb; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import android.database.sqlite.SQLiteStatement; import android.util.Log; public class SqlHelper extends SQLiteOpenHelper { private static final String DATABASE_PATH = "/data/data/com.appfulcrum.blog.examples.listviewcustomdb/databases/"; public static final String DATABASE_NAME = "TODOList"; public static final String TABLE_NAME = "ToDoItems"; public static final int ToDoItems_VERSION = 1; public static final String COLUMN_ID = "_id"; // 0 public static final String COLUMN_TITLE = "title"; // 1 public static final String COLUMN_NAME_DESC = "description";// 2 public static final String COLUMN_SELECTED = "selected"; // 3 public static final int COLUMN_SELECTED_idx = 3; public SQLiteDatabase dbSqlite; private Context mContext; public SqlHelper(Context context) { super(context, DATABASE_NAME, null, 1); mContext = context; } @Override public void onCreate(SQLiteDatabase db) { createDB(db); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w("SqlHelper", "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS ToDoItems;"); createDB(db); } public void createDatabase(SQLiteDatabase db) { createDB(db); } private void createDB(SQLiteDatabase db) { if (db == null) { db = mContext.openOrCreateDatabase(DATABASE_NAME, 0, null); } db.execSQL("CREATE TABLE IF NOT EXISTS ToDoItems (_id INTEGER PRIMARY KEY, title TEXT, " +" description TEXT, selected INTEGER);"); db.setVersion(ToDoItems_VERSION); // // Generate a few rows for an example // // find out how many rows already exist, and make sure there some minimum SQLiteStatement s = db.compileStatement("select count(*) from ToDoItems;"); long count = s.simpleQueryForLong(); for (int i = 0; i < 20-count; i++) { db.execSQL("INSERT INTO ToDoItems VALUES(NULL,'Task #"+i+"','Description #"+i+"',0);"); } } public void openDataBase() throws SQLException { String myPath = DATABASE_PATH + DATABASE_NAME; dbSqlite = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READWRITE); } @Override public synchronized void close() { if (dbSqlite != null) dbSqlite.close(); super.close(); } public Cursor getCursor() { SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); queryBuilder.setTables(TABLE_NAME); String[] asColumnsToReturn = new String[] { COLUMN_ID, COLUMN_TITLE, COLUMN_NAME_DESC, COLUMN_SELECTED }; Cursor mCursor = queryBuilder.query(dbSqlite, asColumnsToReturn, null, null, null, null, COLUMN_ID+" ASC"); return mCursor; } public void clearSelections() { ContentValues values = new ContentValues(); values.put(COLUMN_SELECTED, 0); this.dbSqlite.update(SqlHelper.TABLE_NAME, values, null, null); } } 

Start.java

 //src/Start.java package com.appfulcrum.blog.examples.listviewcustomdb; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.Toast; public class Start extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button btnSimple = (Button) findViewById(R.id.btnSimple); btnSimple.setOnClickListener(new OnClickListener() { public void onClick(View v) { Toast.makeText(getApplicationContext(), " You clicked ListView From DB button", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(v.getContext(), CustomListViewDB.class); startActivityForResult(intent, 0); } }); } } 

layout /main.xml

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/buttonlayout" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="left|top" android:paddingTop="2dp" android:paddingBottom="2dp"> <TextView android:id="@+id/txtTest" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/app_name" android:textSize="15sp" android:textColor="#FF0000" android:gravity="center_vertical" android:paddingLeft="5dp"> </TextView> <Button android:id="@+id/btnSimple" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="15sp" android:text="Listview from DB" android:textColor="#000000" > </Button> </LinearLayout> 

layout /simple.xml

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:id="@+id/buttonlayout" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:height="32dp" android:gravity="left|top" android:paddingTop="2dp" android:paddingBottom="2dp"> <LinearLayout android:id="@+id/buttonlayout2" android:orientation="horizontal" android:layout_height="wrap_content" android:gravity="left|center_vertical" android:layout_width="wrap_content" android:layout_gravity="left|center_vertical"> <TextView android:id="@+id/txtTest" android:layout_width="fill_parent" android:layout_height="fill_parent" android:textStyle="bold" android:text="@string/list_header" android:textSize="15sp" android:gravity="center_vertical" android:paddingLeft="5dp"> </TextView> <Button android:id="@+id/btnClear" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Clear" android:textSize="15sp" android:layout_marginLeft="10px" android:layout_marginRight="10px" android:layout_marginBottom="2px" android:layout_marginTop="2px" android:height="15dp" android:width="70dp"></Button> </LinearLayout> </LinearLayout> <TableLayout android:id="@+id/TableLayout01" android:layout_width="fill_parent" android:layout_height="fill_parent" android:stretchColumns="*"> <TableRow> <ListView android:id="@android:id/list" android:layout_width="wrap_content" android:layout_height="wrap_content"></ListView> </TableRow> </TableLayout> </LinearLayout> 

layout / single _item.xml

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical"> <CheckBox android:id="@+id/bcheck" android:layout_width="wrap_content" android:layout_height="fill_parent" /> <TextView android:id="@+id/txtTitle" android:layout_width="wrap_content" android:gravity="left|center_vertical" android:layout_height="?android:attr/listPreferredItemHeight" android:layout_alignParentLeft="true" android:textSize="20sp" android:text="Test" android:textStyle="bold" android:paddingLeft="5dp" android:paddingRight="2dp" android:focusable="false" android:focusableInTouchMode="false"></TextView> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="right|center_vertical"> </LinearLayout> </LinearLayout> 

<strong> values ​​/strings.xml

 <?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">Hello World, ListViewWithDBActivity!</string> <string name="app_name">ListViewWithDB</string> <string name="list_header">List Headers</string> </resources> 

AndroidManifest.xml

 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.appfulcrum.blog.examples.listviewcustomdb"> <application android:icon="@drawable/icon" android:label="@string/app_name" android:theme="@android:style/Theme.NoTitleBar"> > <activity android:name=".Start" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".CustomListViewDB"></activity> </application> <uses-sdk android:minSdkVersion="7" /> <!-- android 1.6 --> </manifest> 

If you want to build, drop some arbitrary .png icon into drawable.

Thanks in advance.

+7
source share
2 answers

I found the most complete solution to the problem off-site.

On Android ListView with CheckBox: Saving State

Rate people help.

+3
source

The views in the ListView are being redesigned and it sounds like a problem with that. You probably need to invalidate your onCheckedChangedListener so that when you execute setChecked() it does not call the previous listener unintentionally. There may be other consequences of disposal, so keep this in mind.

So try:

 cBox.setOnCheckedChangeListener(null); ... cBox.setChecked(); ... cBox.setOnCheckedChangeListner(<real listener); 
+2
source

All Articles