この文書は本書「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
列名その1title
列名その2simpleitem


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をご覧ください。
目次へ