I am a beginner when it comes to programming with Android/Java, and I am having trouble with what shouldn’t be too difficult to accomplish. I am using code I’ve tried to adapt from the Google Notepad v1. I realize this version is far from complete, but it was the most stripped down to begin on.
What I am trying to do is modify the list to note include notes, but rather make it function like a to-do list, where one would add items to the list. Next to each item, there needs to be a checkbox. I’m writing the checkbox state to the database, and where I am having trouble is gathering the related id from the database for each checkbox. If I manually specify which item I want in the list it works, but I’m unable to get OnClick (setChk) to pull the id I need for the database. I am posting 4 important code files below.
I have had so many problems trying to get this to work, and at this point in programming, I’m just creating things to gain programming skills. If anyone has a functioning to-do list as I’ve described, I would greatly appreciate seeing a working sample code. I greatly appreciate any help!
Finished.java
import android.app.ListActivity;
import android.database.Cursor;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import android.widget.SimpleCursorAdapter;
import android.widget.Toast;
public class Finished extends ListActivity {
public static final int INSERT_ID = Menu.FIRST;
private int mNoteNumber = 1;
private NotesDbAdapter mDbHelper;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.notepad_list);
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
fillData();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
boolean result = super.onCreateOptionsMenu(menu);
menu.add(0, INSERT_ID, 0, R.string.menu_insert);
return result;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case INSERT_ID:
createNote();
return true;
}
return super.onOptionsItemSelected(item);
}
private void createNote() {
String noteName = "Note " + mNoteNumber++;
mDbHelper.createNote(noteName, "", "1");
fillData();
}
private void fillData() {
Cursor c = mDbHelper.fetchAllNotes();
startManagingCursor(c);
String[] from = new String[] { NotesDbAdapter.KEY_TITLE, NotesDbAdapter.KEY_CHECK };
int[] to = new int[] { R.id.text1, R.id.CheckBox1 };
// Now create an array adapter and set it to display using our row
SimpleCursorAdapter notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
notes.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
int nCheckedIndex = cursor.getColumnIndexOrThrow(NotesDbAdapter.KEY_CHECK);
if (columnIndex == nCheckedIndex) {
CheckBox cb = (CheckBox) view;
boolean bChecked = (cursor.getInt(nCheckedIndex) != 0);
cb.setChecked(bChecked);
return true;
}
return false;
}
});
setListAdapter(notes);
}
public void setChk(View view) {
//Below is where I need help
mDbHelper.updateChk(//insert proper number id in list to delete, "1");
//I understand I don't want second value to be 1. It was also just a test
//I need to work on getting the id first before anything else
Toast.makeText(this, "Selected", Toast.LENGTH_SHORT).show();
fillData();
}
}
notes_row.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true">
</TextView>
<CheckBox
android:id="@+id/CheckBox1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:onClick="setChk"
android:text="setChk">
</CheckBox>
</RelativeLayout>
notepad_list.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ListView android:id="@id/android:list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView android:id="@id/android:empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_notes"/>
</LinearLayout>
NotesDbAdapter
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.util.Log;
public class NotesDbAdapter {
public static final String KEY_TITLE = "title";
public static final String KEY_BODY = "body";
public static final String KEY_ROWID = "_id";
public static final String KEY_CHECK = "check";
private static final String TAG = "NotesDbAdapter";
private DatabaseHelper mDbHelper;
private SQLiteDatabase mDb;
/**
* Database creation sql statement
*/
private static final String DATABASE_CREATE =
"create table notes (_id integer primary key autoincrement, "
+ "title text not null, body text not null);";
private static final String DATABASE_NAME = "data";
private static final String DATABASE_TABLE = "notes";
private static final int DATABASE_VERSION = 3;
private final Context mCtx;
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS notes");
onCreate(db);
}
}
/**
* Constructor - takes the context to allow the database to be
* opened/created
*
* @param ctx the Context within which to work
*/
public NotesDbAdapter(Context ctx) {
this.mCtx = ctx;
}
/**
* Open the notes database. If it cannot be opened, try to create a new
* instance of the database. If it cannot be created, throw an exception to
* signal the failure
*
* @return this (self reference, allowing this to be chained in an
* initialization call)
* @throws SQLException if the database could be neither opened or created
*/
public NotesDbAdapter open() throws SQLException {
mDbHelper = new DatabaseHelper(mCtx);
mDb = mDbHelper.getWritableDatabase();
return this;
}
public void close() {
mDbHelper.close();
}
/**
* Create a new note using the title and body provided. If the note is
* successfully created return the new rowId for that note, otherwise return
* a -1 to indicate failure.
*
* @param title the title of the note
* @param body the body of the note
* @return rowId or -1 if failed
*/
public long createNote(String title, String body, String check) {
ContentValues initialValues = new ContentValues();
initialValues.put(KEY_TITLE, title);
initialValues.put(KEY_BODY, body);
initialValues.put(KEY_BODY, check);
return mDb.insert(DATABASE_TABLE, null, initialValues);
}
/**
* Delete the note with the given rowId
*
* @param rowId id of note to delete
* @return true if deleted, false otherwise
*/
public boolean deleteNote(long rowId) {
return mDb.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0;
}
/**
* Return a Cursor over the list of all notes in the database
*
* @return Cursor over all notes
*/
public Cursor fetchAllNotes() {
return mDb.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_TITLE,
KEY_BODY, KEY_CHECK}, null, null, null, null, null, null);
}
/**
* Return a Cursor positioned at the note that matches the given rowId
*
* @param rowId id of note to retrieve
* @return Cursor positioned to matching note, if found
* @throws SQLException if note could not be found/retrieved
*/
public Cursor fetchNote(long rowId) throws SQLException {
Cursor mCursor =
mDb.query(true, DATABASE_TABLE, new String[] {KEY_ROWID,
KEY_TITLE, KEY_BODY, KEY_CHECK}, KEY_ROWID + "=" + rowId, null,
null, null, null, null);
if (mCursor != null) {
mCursor.moveToFirst();
}
return mCursor;
}
/**
* Update the note using the details provided. The note to be updated is
* specified using the rowId, and it is altered to use the title and body
* values passed in
*
* @param rowId id of note to update
* @param title value to set note title to
* @param body value to set note body to
* @param check
* @return true if the note was successfully updated, false otherwise
*/
public boolean updateNote(long rowId, String title, String body, String check) {
ContentValues args = new ContentValues();
args.put(KEY_TITLE, title);
args.put(KEY_BODY, body);
args.put(KEY_CHECK, check);
return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > 0;
}
}
New Finished. java
private void fillData() {
Cursor c = mDbHelper.fetchAllNotes();
startManagingCursor(c);
String[] from = new String[] { NotesDbAdapter.KEY_TITLE, NotesDbAdapter.KEY_CHECK };
int[] to = new int[] { R.id.text1, R.id.CheckBox1 };
// Now create an array adapter and set it to display using our row
final SimpleCursorAdapter notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
notes.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
int nCheckedIndex = cursor.getColumnIndexOrThrow(NotesDbAdapter.KEY_CHECK);
if (columnIndex == nCheckedIndex) {
CheckBox cb = (CheckBox) view;
boolean bChecked = (cursor.getInt(nCheckedIndex) != 0);
cb.setChecked(bChecked);
//call if after setChecked so the listener isn't called there already
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int position = getListView().getPositionForView(buttonView);
Cursor c = notes.getCursor();
if (c.moveToPosition(position)) {
int rowid = c.getInt(c.getColumnIndex(NotesDbAdapter.KEY_ROWID));
if (isChecked) {
mDbHelper.updateChk(rowid, "1");
} else {
mDbHelper.updateChk(rowid, "0");
}
}
}
});
return true;
}
return false;
}
});
setListAdapter(notes);
}
Logcat
03-11 10:38:53.486: E/AndroidRuntime(5611): FATAL EXCEPTION: main
03-11 10:38:53.486: E/AndroidRuntime(5611): java.lang.NullPointerException
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.widget.AdapterView.getPositionForView(AdapterView.java:581)
03-11 10:38:53.486: E/AndroidRuntime(5611): at com.finished.Finished$1$1.onCheckedChanged(Finished.java:167)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.widget.CompoundButton.setChecked(CompoundButton.java:124)
03-11 10:38:53.486: E/AndroidRuntime(5611): at com.finished.Finished$1.setViewValue(Finished.java:162)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:126)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.widget.CursorAdapter.getView(CursorAdapter.java:186)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.widget.AbsListView.obtainView(AbsListView.java:1409)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.widget.ListView.makeAndAddView(ListView.java:1745)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.widget.ListView.fillUp(ListView.java:700)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.widget.ListView.correctTooHigh(ListView.java:1367)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.widget.ListView.fillGap(ListView.java:642)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.widget.AbsListView.trackMotionScroll(AbsListView.java:3399)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.widget.AbsListView.onTouchEvent(AbsListView.java:2233)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.widget.ListView.onTouchEvent(ListView.java:3446)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.view.View.dispatchTouchEvent(View.java:3885)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:903)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
03-11 10:38:53.486: E/AndroidRuntime(5611): at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1691)
03-11 10:38:53.486: E/AndroidRuntime(5611): at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1125)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.app.Activity.dispatchTouchEvent(Activity.java:2096)
03-11 10:38:53.486: E/AndroidRuntime(5611): at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1675)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.view.ViewRoot.deliverPointerEvent(ViewRoot.java:2194)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.view.ViewRoot.handleMessage(ViewRoot.java:1878)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.os.Handler.dispatchMessage(Handler.java:99)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.os.Looper.loop(Looper.java:123)
03-11 10:38:53.486: E/AndroidRuntime(5611): at android.app.ActivityThread.main(ActivityThread.java:3683)
03-11 10:38:53.486: E/AndroidRuntime(5611): at java.lang.reflect.Method.invokeNative(Native Method)
03-11 10:38:53.486: E/AndroidRuntime(5611): at java.lang.reflect.Method.invoke(Method.java:507)
03-11 10:38:53.486: E/AndroidRuntime(5611): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
03-11 10:38:53.486: E/AndroidRuntime(5611): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
03-11 10:38:53.486: E/AndroidRuntime(5611): at dalvik.system.NativeStart.main(Native Method)
First make the
SimpleCursorAdaptera instance variable of your class. Than change setChk to the following.The
OnClickListenerisn’t the best choice in your case. AnOnCheckedChangeListeneris better for receiving a click on a CheckBox. It also has the advantage that you don’t have to call yourfillData()method again, which can take some time if your db is large.Edit:
I modified the code of your ViewBinder to use a
OnCheckedChangeListener: