保存To-Do List
在本章的前面,你增强了To-Do List例子的功能,能够在会话间保存Activity UI的状态。那只是一半的工作;在接下来的例子里,你将创建一个私有的数据库来保存那些to-do项:
1. 创建一个新的ToDoDBAdapter类。它将用于管理你的数据库交互。
创建私有的变量来储存SQLiteDatabase对象和调用程序的上下文。添加构造函数来得到所属程序的上下文,并且为数据库的名和版本以及to-do项目的表添加静态的数据成员。
package com.paad.todolist;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class ToDoDBAdapter
{
private static final String DATABASE_NAME = “todoList.db”;
private static final String DATABASE_TABLE = “todoItems”;
private static final int DATABASE_VERSION = 1;
private SQLiteDatabase db;
private final Context context;
public ToDoDBAdapter(Context _context)
{
this.context = _context;
}
}
2. 创建公共的变量来定义列名和列索引;这使得在通过Cursor提取值时更加容易地找到正确的列。
public static final String KEY_ID = “_id”;
public static final String KEY_TASK = “task”;
public static final int TASK_COLUMN = 1;
public static final String KEY_CREATION_DATE = “creation_date”;
public static final int CREATION_DATE_COLUMN = 2;
3. 在toDoDBAdapter中创建SQLiteopenHelper类的扩展——toDoDBOpenHelper。它将用于简化数据库的版本管理。在其中,重写onCreate和onUpgrade方法来处理数据库的创建和更新逻辑。
private static class toDoDBOpenHelper extends SQLiteOpenHelper
{
public toDoDBOpenHelper(Context context, String name, CursorFactory factory, int version)
{
super(context, name, factory, version);
}
// SQL Statement to create a new database.
private static final String DATABASE_CREATE = “create table “ +
DATABASE_TABLE + “ (“ + KEY_ID +
“ integer primary key autoincrement, “ +
KEY_TASK + “ text not null, “ + KEY_CREATION_DATE + “ long);”;
@Override
public void onCreate(SQLiteDatabase _db)
{
_db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase _db, int _oldVersion, int _newVersion)
{
Log.w(“TaskDBAdapter”, “Upgrading from version “ +
_oldVersion + “ to “ + _newVersion +
“, which will destroy all old data”);
// Drop the old table.
_db.execSQL(“DROP TABLE IF EXISTS “ + DATABASE_TABLE);
// Create a new one.
onCreate(_db);
}
}
4. 在toDoDBAdapter类中,添加一个私有变量来储存toDoDBOpenHelper类的实例,并在构造函数中创建它。
private toDoDBOpenHelper dbHelper;
public ToDoDBAdapter(Context _context)
{
this.context = _context;
dbHelper = new toDoDBOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
}
5. 还在toDoDBAdapter类中,创建open和close方法。在close方法中简单的调用数据库对象的close方法。
public void close() {
db.close();
}
6. open方法需要使用toDoDBOpenHelper类。调用getWriteableDatabase来让辅助类处理数据库的创建和版本检查。当可写的数据库不能被打开时,尝试提供一个可读数据库。
public void open() throws SQLiteException {
try
{
db = dbHelper.getWritableDatabase();
}
catch (SQLiteException ex)
{
db = dbHelper.getReadableDatabase();
}
}
7. 为添加、删除和更新项目添加强类型方法。
// Insert a new task
public long insertTask(ToDoItem _task)
{
// Create a new row of values to insert.
ContentValues newTaskValues = new ContentValues();
// Assign values for each row.
newTaskValues.put(KEY_TASK, _task.getTask());
newTaskValues.put(KEY_CREATION_DATE, _task.getCreated().getTime());
// Insert the row.
return db.insert(DATABASE_TABLE, null, newTaskValues);
}
// Remove a task based on its index
public boolean removeTask(long _rowIndex)
{
return db.delete(DATABASE_TABLE, KEY_ID + “=” + _rowIndex, null) > 0;
}
// Update a task
public boolean updateTask(long _rowIndex, String _task)
{
ContentValues newValue = new ContentValues();
newValue.put(KEY_TASK, _task);
return db.update(DATABASE_TABLE, newValue, KEY_ID + “=” + _rowIndex, null) > 0;
}
8. 现在,添加辅助方法来处理查询。写三个方法——第一个返回所有的项目,第二个返回指定行的Cursor,最后一个返回强类型的ToDoItem对象。
public Cursor getAllToDoItemsCursor()
{
return db.query(DATABASE_TABLE, new String[] { KEY_ID, KEY_TASK, KEY_CREATION_DATE},
null, null, null, null, null);
}
public Cursor setCursorToToDoItem(long _rowIndex) throws SQLException
{
Cursor result = db.query(true, DATABASE_TABLE, new String[] {KEY_ID, KEY_TASK},
KEY_ID + “=” + _rowIndex, null, null, null, null, null);
if ((result.getCount() == 0) || !result.moveToFirst())
{
throw new SQLException(“No to do items found for row: “ + _rowIndex);
}
return result;
}
public ToDoItem getToDoItem(long _rowIndex) throws SQLException
{
Cursor cursor = db.query(true, DATABASE_TABLE, new String[] {KEY_ID, KEY_TASK},
KEY_ID + “=” + _rowIndex, null, null, null, null, null);
if ((cursor.getCount() == 0) || !cursor.moveToFirst())
{
throw new SQLException(“No to do item found for row: “ + _rowIndex);
}
String task = cursor.getString(TASK_COLUMN);
long created = cursor.getLong(CREATION_DATE_COLUMN);
ToDoItem result = new ToDoItem(task, new Date(created));
return result;
}
9. 以上完成了数据库的辅助类。回到ToDoList Activity中,并且更新to-do list数组的内容。
在onCreate方法中创建一个toDoDBAdapter的实例,并且打开一个数据库。同时,调用populateToDoList方法。
ToDoDBAdapter toDoDBAdapter;
public void onCreate(Bundle icicle)
{
[ ... existing onCreate logic ... ]
toDoDBAdapter = new ToDoDBAdapter(this);
// Open or create the database
toDoDBAdapter.open();
populateTodoList();
}
private void populateTodoList() { }
10. 创建一个变量来储存指向数据库中所有的to-do项目的Cursor。更新populateTodoList方法,使用toDoDBAdapter实例来查询数据库,并调用startManagingCursor来让Activity管理Cursor。它还应该调用updateArray,该方法使用Cursor重新填入to-do list数组。
Cursor toDoListCursor;
private void populateTodoList()
{
// Get all the todo list items from the database.
toDoListCursor = toDoDBAdapter.getAllToDoItemsCursor();
startManagingCursor(toDoListCursor);
// Update the array.
updateArray();
}
private void updateArray() { }
11. 现在,实现updateArray方法来更新当前的to-dolist数组。调用requery来保证数据库更新了,然后清除数组并枚举整个结果集。当完成时,调用notifyDataSetChanged方法。
private void updateArray()
{
toDoListCursor.requery();
todoItems.clear();
if (toDoListCursor.moveToFirst())
do
{
String task = toDoListCursor.getString(ToDoDBAdapter.TASK_COLUMN);
long created = toDoListCursor.getLong(ToDoDBAdapter.CREATION_DATE_COLUMN);
ToDoItem newItem = new ToDoItem(task, new Date(created));
todoItems.add(0, newItem);
} while(toDoListCursor.moveToNext());
aa.notifyDataSetChanged();
}
12. 为了将各块粘合起来,修改OnKeyListener方法和更新removeItem方法。都使用toDoDBAdapter来在数据库中增加和删除项目,而不再直接修改to-do list数组。
12.1. 在OnKeyListener中,往数据库中插入新的项目,并更新数组。
public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
setContentView(R.layout.main);
myListView = (ListView)findViewById(R.id.myListView);
myEditText = (EditText)findViewById(R.id.myEditText);
todoItems = new ArrayList<ToDoItem>();
int resID = R.layout.todolist_item;
aa = new ToDoItemAdapter(this, resID, todoItems);
myListView.setAdapter(aa);
myEditText.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_DOWN)
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
{
ToDoItem newItem;
newItem = new ToDoItem(myEditText.getText().toString());
toDoDBAdapter.insertTask(newItem);
updateArray();
myEditText.setText(“”);
aa.notifyDataSetChanged();
cancelAdd();
return true;
}
return false;
}
});
registerForContextMenu(myListView);
restoreUIState();
toDoDBAdapter = new ToDoDBAdapter(this);
// Open or create the database
toDoDBAdapter.open();
populateTodoList();
}
12.2. 然后修改removeItem来从数据库中删除项目,并更新数组列表。
private void removeItem(int _index) {
// Items are added to the listview in reverse order,
// so invert the index.
toDoDBAdapter.removeTask(todoItems.size()-_index);
updateArray();
}
13. 最后一步,重写onDestroy方法,关闭数据库的连接。
@Override
public void onDestroy() {
// Close the database
toDoDBAdapter.close();
super.onDestroy();
}
现在,你的to-do项目将在会话间保存了。作为下一步的提高,你可以改变ArrayAdapter为CursorAdapter,并让ListView直接依据底层数据的变化动态更新。
通过使用私有的数据库,你的任务现在不能被别的应用程序看见或是添加。让了能够让其它应用程序来访问你的任务,你可以使用Content Provider来暴露它们。