この文書は本書「Androidアプリ開発逆引き大全500の極意」(秀和システム刊、ISBN978-4-7980-3734-9)で紹介しきれなかった内容をまとめたものです。
目次へ
清水美樹の本 トップページへ
データベースアプリのソースコード
「SimpleDB」のコードをもっとつなげて示します
本書で解説したもっとも簡単なデータベースアプリ「SimpleDB」(Tips394-425)の補足説明をします。
本書では、ピンポイントのTipsを詳しく説明しましたが、書籍の性格上、コードが切れ切れになっていました。
そこで、この付録文書では、コードをより長くまとめて表示します。ただし、あまり長くなるとこれはこれでわからなくなってしまうので、適当に区切ります。
本書と合わせてお読みください
各部の説明は本書に掲載しておりますので、この文書と合わせてお読みください。
中途半端に簡単なデータベース操作です
なお、このアプリは、SDKのサンプル「NotePad」(Tips139)のソースコードになるべく沿いながら、かつなるべく簡単にコードを省略したものです。
ゆえに、性格は「どっちつかず」で,実用的ではありません。あくまで、サンプルの「NotePad」のコードの理解を深めるためのものですので、「NotePad」と照らし合わせながら研究してください。
API11より前のメソッドを用います(が、API17のサンプルでも使っています)
API11以上のデータベースアプリでは、メソッドmanagedQuery(Tips374)は非推奨であり、「カーソルローダー」を用いてカーソルを制御する方法が推奨されています。そこのところは本書でも解説してあります(Tips379-382, 414)ので、ここでは簡単に考えるため「managedQuery」でクエリを発行することにします。
ただし、SDKのサンプル「NotePad」についても、実はAPI17のサンプルでありながら「managedQuery」を使っていますので、古いAPIのサンプルを入手する(Tips016)必要はありません。
データを記述するクラス「Simple」
データベースの構造
データベースの構造は本書で解説済みですが、以下に再掲しますので確認していただければと思います。なお、列title, simpleitemの値はいずれも文字列型です。
データベース名 | simpledb |
テーブル名 | simpleitems |
列名その1 | title |
列名その2 | simpleitem |
Simple.java全文
「Simple.java」の内容は、本書でかなり詳しく解説したと思いますが、「全文」でつながりを示したほうがよいと思います。
以下のリスト1が、「Simple.java」の全文です。ただし、パッケージやインポートの宣言は省略してあります(Eclipseが自動で記入してくれます - Tips121)。
リスト1 Simple.javaの全文(パッケージやインポートの宣言は省略)
public final class Simple {
/**
* 同じコンテントプロバイダで複数のテーブルを用いる場合、
* ここまでは共通
*/
public static final String AUTHORITY = "net.supportdoc.provider.Simple";
/**
* このクラスは、「ひとつのオブジェクト」を作成するためのものなので、
*「コンストラクタで何もしない」ことを明記
*/
private Simple() {}
/**
* 他のクラスからよく使う
* この内部クラス「Simple.SimpleColumns」の各フィールド
*/
public static final class SimpleColumns implements BaseColumns {
private SimpleColumns() {}
/**
* テーブル名をSimple.SimpleColumns.CONTENT_URIで表す
*/
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/simpleitems");
/**
* 「データのタイプ」のうち、「複数のデータ」を表す
* Simple.SimpleColumns.CONTENT_TYPEで表す
*/
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.simple.simpleitem";
/**
* 「データのタイプ」のうち、「個々のデータ」を表す
* Simple.SimpleColumns.CONTENT_ITEM_TYPEで表す
*/
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/vnd.simple.simpleitem";
/**
* 列名「title」をSimple.SimpleColumns.TITLEで表す
*
*/
public static final String TITLE = "title";
/**
* 列名「title」をSimple.SimpleColumns.SIMPLEITEMで表す
*
*/
public static final String SIMPLEITEM = "simpleitem";
}
}
|
データベースプロバイダ「SimpleDBProvider」
SimpleDBProviderの構造
クラスSimpleDBProviderの定義の構造はなかなか複雑です。特に、「データベースヘルパー」クラスを「内部クラス」として定義し、それを使うというところが見えにくいと思いますので、まず図1に構造を示します。本章のTips(Tips397-411)と照らし合わせながら眺めてください。
図1 クラス「SimpleDBProvider」の定義の構造
定数、静的フィールドの定義
データベースの作成・操作に用いる定数を、リスト2のように定義します。
リスト2 データベースの作成・操作に用いる定数
private static final String DATABASE_NAME = "simpleitem.db";
private static final int DATABASE_VERSION = 1;
private static final String SIMPLEITEMS_TABLE_NAME = "simpleitems";
|
プロジェクション・マップ
「プロジェクション・マップ」をリスト3のように定義します。(「プロジェクション」についてはTips376)
リスト3 変数simpleProjectionMapを定義
private static HashMap simpleProjectionMap;
//値をセットする
static {
simpleProjectionMap = new HashMap();
simpleProjectionMap.put(SimpleColumns._ID, SimpleColumns._ID);
simpleProjectionMap.put(SimpleColumns.TITLE, SimpleColumns.TITLE);
simpleProjectionMap.put(SimpleColumns.SIMPLEITEM, SimpleColumns.SIMPLEITEM);
}
|
リスト3は、キーと値の組み合わせに、全く同じ文字列を使っていますね。
実は、「プロジェクションマップ」は、「本当の列名の代わりに別の列名を用いたい場合」ためのものです。既存のテーブルを再利用する時などに便利ですが、ここでは特に別名を使わないので、本当の列名と本当の列名の対照になっています。
URIマッチャー
「URIマッチャー(Tips404)」をリスト4のように定義します。
リスト6 URIマッチャーの定義
private static final UriMatcher sUriMatcher;
private static final int SIMPLEITEMS = 1;
private static final int SIMPLEITEM_ID = 2;
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(Simple.AUTHORITY, "simpleitems", SIMPLEITEMS);
sUriMatcher.addURI(Simple.AUTHORITY, "simpleitems/#", SIMPLEITEM_ID);
}
|
内部クラスDatabaseHelperの定義
DatabaseHelperの定義(Tips397-398)はリスト7の通りです。
リスト7 内部クラスDatabaseHelperの定義
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + SIMPLEITEMS_TABLE_NAME + " ("
+ SimpleColumns._ID + " INTEGER PRIMARY KEY,"
+ SimpleColumns.TITLE + " TEXT,"
+ SimpleColumns.SIMPLEITEM + " TEXT"
+ ");");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//oldVersion, newVersionを表示するなどの処理を行うとよい
db.execSQL("DROP TABLE IF EXISTS simpleitems");
onCreate(db);
}
}
|
DataBaseHelperオブジェクトの作成
リスト8のように、DataBaseHelperオブジェクトを作成しメンバー変数mOpenHelperに保持させます。(Tips400)
リスト8 mOpenHelperを使って操作をする準備
private DatabaseHelper mOpenHelper;
@Override
public boolean onCreate() {
mOpenHelper = new DatabaseHelper(getContext());
return true;
}
|
insertメソッド
mOpenHelperを用いてデータベーステーブルに行を挿入するメソッドです。戻り値は今挿入したデータのURIです。(Tips401)
リスト 9 insertメソッド
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowId = db.insert(
SIMPLEITEMS_TABLE_NAME, SimpleColumns.SIMPLEITEM, values);
if (rowId > 0) {
Uri simpleUri = ContentUris.withAppendedId(
SimpleColumns.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(simpleUri, null);
//成功したら、ここで抜ける
return simpleUri;
}
//失敗したら、ここまで残る
return null;
}
|
queryメソッド
sUriMatcherを用いて「複数の行データ」または「ひとつの行データ」に場合分けして問い合わせるメソッドです。戻り値はカーソルオブジェクトです。(Tips403)
リスト10 queryメソッド
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(SIMPLEITEMS_TABLE_NAME);
switch (sUriMatcher.match(uri)) {
//複数の行データが求められているならば
case SIMPLEITEMS:
//検索条件をつけない(このあと、selectionなどで改めてつける)
break;
//単一の行データが求められているならば
case SIMPLEITEM_ID:
//IDでデータを指定できるので、ここで検索条件につける
//なお、IDはURIで指定されてくる
qb.appendWhere(SimpleColumns._ID + "=" +
uri.getPathSegments().get(1));
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
//クエリを発行
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
Cursor c = qb.query(
db, projection, selection, selectionArgs, null, null, null);
//URI情報をカーソルに与えておく。どこか他でデータが更新された場合に対応できる
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
|
updateメソッド, deleteメソッド
sUriMatcherを用いて「複数の行データ」または「ひとつの行データ」に場合分けして更新/削除するメソッドです。戻り値は変更されたデータの数です。(Tips402)
リスト10 updateメソッド
@Override
public int update(Uri uri, ContentValues values,
String where, String[] whereArgs){
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
switch (sUriMatcher.match(uri)) {
case SIMPLEITEMS:
count = db.update(SIMPLEITEMS_TABLE_NAME, values, where, whereArgs);
break;
case SIMPLEITEM_ID:
String simpleId = uri.getPathSegments().get(1);
count = db.update(
SIMPLEITEMS_TABLE_NAME, values,
SimpleColumns._ID + "=" + simpleId
+ (!TextUtils.isEmpty(where) ? " AND (" + where +
')' : ""), whereArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
|
リスト11 deleteメソッド
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
switch (sUriMatcher.match(uri)) {
case SIMPLEITEMS:
count = db.delete(SIMPLEITEMS_TABLE_NAME, where, whereArgs);
break;
case SIMPLEITEM_ID:
String noteId = uri.getPathSegments().get(1);
count = db.delete(SIMPLEITEMS_TABLE_NAME, SimpleColumns._ID + "=" + noteId
+ (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
|
getTypeメソッド
使いどころがいまひとつわからないメソッドです。データタイプを判定したsURIMatcherの判定値を、再びデータタイプを表す文字列に変換するものです。他のクラスからデータタイプ参照するためと考えればよいと思います。(Tips411)
リスト12 getTypeメソッド
@Override
public String getType(Uri uri) {
switch (sUriMatcher.match(uri)) {
case SIMPLEITEMS:
return SimpleColumns.CONTENT_TYPE;
case SIMPLEITEM_ID:
return SimpleColumns.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
|
一覧ページ「SimpleList」
SimpleListの構造
アクティビティ(ListActivity)「SimpleList」(Tips412)はアプリ「SimpleDB」の起動画面であり、データベーステーブルsimpleitemsの全内容をリスト表示するものです。
その大まかな構造をリスト13に示します。
リスト13 「SimpleList」の具体的な構造
public class SimpleList extends ListActivity {
onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//リストの更新。起動時はnullなので適切に処置する
Intent intent = getIntent();
if (intent.getData() == null) {
....
}
Cursor cursor = managedQuery....
SimpleCursorAdapter adapter =....
setListAdapter(adapter);
}
//オプションメニューで「追加」を選べるようにする
onCreateOptionsMenu
onOptionsItemSelected
onListItemClick
}
|
「プロジェクション」を定数に
一覧は「title」列のみを表示しますから、定数PROJECTIONは「ID」と「TITLE」だけでよいことになります。
リスト14 定数PROJECTIONの定義
private static final String[] PROJECTION = new String[] {
SimpleColumns._ID, // 0
SimpleColumns.TITLE, // 1
};
|
onCreateメソッド
onCreateメソッドの全文はリスト15の通りです。なお、リスト15にはonCreateメソッドなどで用いるメンバ変数mWhereの定義も添えて表示します。
リスト15 mWhereの定義と、onCreateメソッド
String mWhere;
//古いメソッドなので、コンパイル時警告が出ないようにしてしまう
@SuppressWarnings("deprecation")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWhere=null;
Intent intent = getIntent();
if (intent.getData() == null) {
intent.setData(SimpleColumns.CONTENT_URI);
}
Cursor cursor = managedQuery(getIntent().getData(),
PROJECTION, mWhere, null, null);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1, cursor,
new String[] { SimpleColumns.TITLE },
new int[] { android.R.id.text1 });
setListAdapter(adapter);
}
|
オプションメニューの設定
「新規作成」画面に入るのに、オプションメニューを用いる方法を示します。(Tips423)
リスト16 onCreateOptionsMenu(メニューを表示)
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.list_option, menu);
return super.onCreateOptionsMenu(menu);
}
|
リスト17 onOptionsItemSelected(メニュー選択時の動作)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_add:
startActivity(new Intent(Intent.ACTION_INSERT, getIntent().getData()));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
|
onListItemClickメソッド
リストをクリックして、編集画面に入る方法を示します。(Tips418)
リスト18 onListItemClickメソッド
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Uri simpleUri = ContentUris.withAppendedId(getIntent().getData(), id);
startActivity(new Intent(Intent.ACTION_EDIT, simpleUri));
}
|
編集ページ「SimpleEditor」
SimpleEditorの構造
アクティビティ「SimpleEditor」は、データの編集・新規作成の両方に使える画面です。(Tips422)
その大まかな構造をリスト19に示します。
リスト19 SimpleEditorの構造
public class SimpleEditor extends Activity {
onCreate
画面の準備
final Intent intent = getIntent();
final String action = intent.getAction();
if (Intent.ACTION_EDIT.equals(action)) {
インテントから URIを得てmUriに割り当てる
} else if (Intent.ACTION_INSERT.equals(action)) {
空の値をinsertして、戻り値をmUriに割り当てる
} else {
なんらかの不具合の場合、黙って閉じる
}
mUriをもとにクエリを発行
}
onResume
編集モードの場合、元のデータを入力欄にセットする
onCreateOptionsMenu
onOptionsItemSelected
下に定義するsaveItemを使う
自分で定義するメソッドsaveItem
}
|
「プロジェクション」を定数に
編集画面は「title」「simpleItem」のいずれの列の値をも扱います。
リスト20 定数PROJECTIONの定義
private static final String[] PROJECTION = new String[] {
SimpleColumns._ID, // 0
SimpleColumns.TITLE, // 1
SimpleColumns.SIMPLEITEM // 2
};
|
どちらの列の値を扱うかを、リスト20の配列のインデックスで示します。すなわち、リスト21のようになります。
リスト21 PROJECTIONのインデックスを定数に
private static final int COLUMN_INDEX_TITLE = 1;
private static final int COLUMN_INDEX_SIMPLEITEM = 2;
|
編集か新規作成か
編集モードか新規作成モードかを定数で区別します。。
リスト22 編集モードか新規作成モードかを示す定数
private static final int STATE_EDIT = 0;
private static final int STATE_INSERT = 1;
|
メンバー変数を定義
以下のメンバー変数を定義します。
リスト23 メンバー変数を定義
private int mState;
private Uri mUri;
private Cursor mCursor;
private EditText mTextTitle;
private EditText mTextItem;
|
onCreateメソッド
以下がonCreateメソッドの全文です。
リスト24 onCreateメソッド
@SuppressWarnings("deprecation")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simpleeditor);
mTextTitle = (EditText) findViewById(R.id.editTitle);
mTextItem = (EditText) findViewById(R.id.editSimpleItem);
final Intent intent = getIntent();
final String action = intent.getAction();
if (Intent.ACTION_EDIT.equals(action)) {
mState = STATE_EDIT;
mUri = intent.getData();
} else if (Intent.ACTION_INSERT.equals(action)) {
mState = STATE_INSERT;
//空の値をデータベースに挿入して、Uriを得ておく
//あとは、新規・編集に関係なく、updateメソッドを使えばよい
mUri = getContentResolver().insert(intent.getData(), null);
//作業に失敗したときは閉じる
if (mUri == null) {
finish();
return;
}
} else {
//そのほか、アクションの指定に不備があった場合
finish();
return;
}
mCursor = managedQuery(mUri, PROJECTION, null, null, null);
};
|
onResumeメソッド
編集モードのとき、編集する値を画面にセットします。起動時にも呼ばれます(Tips266)ので、編集画面がこれで完成します。
リスト24 onCreateメソッド
@SuppressWarnings("deprecation")
protected void onResume() {
super.onResume();
if (mCursor != null) {
mCursor.requery();
mCursor.moveToFirst();
if (mState == STATE_EDIT) {
// 編集画面モードの場合、ウィンドウタイトルに「編集」と表示
String title = mCursor.getString(COLUMN_INDEX_TITLE);
setTitle(title + ":" + getString(R.string.label_edit));
mTextTitle.setText(title);
//入力欄に、読みだした値をセット
String simpleitem = mCursor.getString(COLUMN_INDEX_SIMPLEITEM);
mTextItem.setText(simpleitem);
} else if (mState == STATE_INSERT) {
//新規作成モードの場合、ウィンドウタイトルに「新規作成」と表示
setTitle(getText(R.string.label_insert));
}
}
}
|
オプションメニューで保存
このアプリでは、オプションメニューで値を保存することにします。
リスト25 値を保存するためのオプションメニューを作成
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.list_option, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_save:
saveItem();
finish();
break;
}
return super.onOptionsItemSelected(item);
}
|
saveItemメソッド
自分で定義するメソッドです。実際に値を保存します。(Tips424)
リスト26 saveItemメソッド
private final void saveItem() {
if (mCursor != null) {
ContentValues values = new ContentValues();
String title = mTextTitle.getText().toString();
values.put(SimpleColumns.TITLE, title);
String simpleitem = mTextItem.getText().toString();
values.put(SimpleColumns.SIMPLEITEM, simpleitem);
try {
getContentResolver().update(mUri, values, null, null);
} catch (NullPointerException e) {
//エラーメッセージを出すなどする
}
}
}
|
以上がデータベースアプリに必要なコード例です。ピンポイントの説明は、文中に示した本書のTipsをご覧ください。
目次へ