2013年04月12日

Androidアプリでデータ保存、その3【Database編】

前々回はAndroidでKey-Valueデータ保存方法を、前回はAndroidでのファイル保存方法を紹介した。

というわけで今回はAndroidの3つ目のデータ永続化法である、「データベース」の扱い方を紹介したいと思う。

Androidでは軽量データベースでおなじみのSQLiteが使われている。


スキーマとコントラクトの定義

まずはスキーマとコントラクトを定義する必要がある。

まずはスキーマ。

CREATE TABLE messages (
 _ID INTEGER PRIMARY KEY,
 messages_id TEXT,
 message TEXT
);
_ID というカラムはAndroidのフレームワーク側で利用されるカラムなので、暗黙の了解として定義しておこう。ちなみにこのカラムを定義しなくてもなんとなく動いたけど、定義しておくに越したことはないだろう。

次にコントラクト。

MessagesContract.java
package com.example.myfirstapp;

import android.provider.BaseColumns;

public class MessagesContract {

	/**
	 * インスタンス化防止
	 */
	private MessagesContract() {
	}

	public abstract class Messages implements BaseColumns {
		// テーブル名
		public static final String TABLE_NAME = "messages";
		// 「_ID」はandroidアプリのお決まりらしいので変更または削除しないこと。
		public static final String _ID = "_ID";
		// カラム名1
		public static final String COLUMN_NAME_MESSAGES_ID = "messages_id";
		// カラム名2
		public static final String COLUMN_NAME_MESSAGE = "message";
	}
}


DbHelper (SQL Helper)

データベースを操作するためのヘルパークラスを作る。

MessagesDbHelper.java
package com.example.myfirstapp;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class MessagesDbHelper extends SQLiteOpenHelper {
	private static final String TEXT_TYPE = " TEXT";
	private static final String SQL_CREATE_MESSAGES = "CREATE TABLE "
			+ MessagesContract.Messages.TABLE_NAME + " ("
			+ MessagesContract.Messages._ID + " INTEGER PRIMARY KEY,"
			+ MessagesContract.Messages.COLUMN_NAME_MESSAGES_ID + TEXT_TYPE
			+ "," + MessagesContract.Messages.COLUMN_NAME_MESSAGE + TEXT_TYPE
			+ " )";

	private static final String SQL_DELETE_MESSAGES = "DROP TABLE IF EXISTS "
			+ MessagesContract.Messages.TABLE_NAME;

	// If you change the database schema, you must increment the database
	// version.
	public static final int DATABASE_VERSION = 1;
	public static final String DATABASE_NAME = "Messages.db";

	public MessagesDbHelper(Context context) {
		super(context, DATABASE_NAME, null, DATABASE_VERSION);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL(SQL_CREATE_MESSAGES);

	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		// 今回は特にデータエクスポート処理は実装してない
		// テーブルをDropして作り直してるだけ
		db.execSQL(SQL_DELETE_MESSAGES);
		onCreate(db);
	}

	@Override
	public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		// onUpgradeを読んでるだけ
		onUpgrade(db, oldVersion, newVersion);
	}

}

※DbHelperのonUpgrade(またはonDowngrade)メソッドにはデータベースのスキーマのバージョンアップ(またはバージョンダウン)時にデータエクスポート機能などを実装することを想定している。


次は画面の定義。

activity_main.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="fill_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <EditText
            android:id="@+id/edit_message"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="@string/edit_message" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="sendMessage"
            android:text="@string/button_send" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="saveToSP"
            android:text="@string/button_save_to_sp" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="readFromSP"
            android:text="@string/button_read_from_sp" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="saveToFile"
            android:text="@string/button_save_to_file" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="readFromFile"
            android:text="@string/button_read_from_file" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="deleteFile"
            android:text="@string/button_delete_file" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="saveToDb"
            android:text="@string/button_save_to_db" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="readFromDb"
            android:text="@string/button_read_from_db" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="deleteFromDb"
            android:text="@string/button_delete_from_db" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="updateDb"
            android:text="@string/button_update_db" />
    </LinearLayout>

</LinearLayout>

今回使っているのは、入れ子になっている4つ目のLinearLayout。


そしてリソースを定義。

strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">My First App</string>
    <string name="edit_message">Enter a message</string>
    <string name="button_send">Send</string>
    <string name="menu_settings">Settings</string>
    <string name="title_activity_main">MainActivity</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Hello world!</string>
    <string name="title_activity_display_message">My Message</string>

    <!-- SharedPreferences -->
    <string name="sp_key">my_sp_key</string>
    <string name="button_save_to_sp">save to SP</string>
    <string name="button_read_from_sp">read from SP</string>

    <!-- File -->
    <string name="button_save_to_file">save to file</string>
    <string name="button_read_from_file">read from file</string>
    <string name="button_delete_file">delete file</string>

    <!-- Db -->
    <string name="button_save_to_db">DBへ保存</string>
    <string name="button_read_from_db">DBから読込</string>
    <string name="button_update_db">DBを更新(LIKE \u0025aaa\u0025)</string>
    <string name="button_delete_from_db">DBから全削除</string>

</resources>


最後はアクティビティ。

MainActivity.java
package com.example.myfirstapp;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Date;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {
	public final static String EXTRA_MESSAGE = "com.example.myfirstapp.MESSAGE";

	// InstanceStateへ状態を保存するためのキー
	public final static String STATE_ENTERED_MESSAGE = "state.enteredmessage";

	private String message = null;

	private final static String FILENAME = "myfile.txt";

	// DbHelper
	private MessagesDbHelper mDbHelper = new MessagesDbHelper(this);

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	/** Called when the user clicks the Send button */
	public void sendMessage(View view) {
		Intent intent = new Intent(this, DisplayMessageActivity.class);
		EditText editText = (EditText) findViewById(R.id.edit_message);
		message = editText.getText().toString();
		intent.putExtra(EXTRA_MESSAGE, message);
		startActivity(intent);
	}

	/**
	 * メッセージをSharedPreferencesへ保存
	 */
	public void saveToSP(View view) {
		// 入力ボックスの値を取得
		EditText editText = (EditText) findViewById(R.id.edit_message);
		message = editText.getText().toString();
		// SPへ保存
		SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
		SharedPreferences.Editor editor = sharedPref.edit();
		editor.putString(getString(R.string.sp_key), message);
		editor.commit();
		// トースト表示
		Toast.makeText(this, "SPへ保存しました:" + message, Toast.LENGTH_LONG).show();
	}

	/**
	 * SharedPreferencesに保存されたメッセージを読み込み
	 */
	@SuppressLint("ShowToast")
	public void readFromSP(View view) {
		// SPからデータを取得
		SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
		String message = sharedPref.getString(getString(R.string.sp_key),
				"default message");
		// トースト表示
		Toast.makeText(this, "SPから読み込みました:" + message, Toast.LENGTH_LONG)
				.show();
	}

	/**
	 * ファイルへ保存
	 * 
	 * @param view
	 */
	@SuppressLint("ShowToast")
	public void saveToFile(View view) {
		// 入力ボックスの値を取得
		EditText editText = (EditText) findViewById(R.id.edit_message);
		message = editText.getText().toString();

		try {
			FileOutputStream fos = openFileOutput(FILENAME,
					Context.MODE_PRIVATE);
			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos));
			bw.write(message);
			bw.flush();
			bw.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

		// トースト表示
		Toast.makeText(this, "saved to file:" + message, Toast.LENGTH_SHORT)
				.show();
	}

	/**
	 * ファイルから読み込み
	 * 
	 * @param view
	 */
	@SuppressLint("ShowToast")
	public void readFromFile(View view) {

		FileInputStream file;
		String str = null;
		try {
			file = openFileInput(FILENAME);
			BufferedReader br = new BufferedReader(new InputStreamReader(file));
			str = br.readLine();
			br.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

		// トースト表示
		Toast.makeText(this, "read from file:" + str, Toast.LENGTH_LONG).show();
	}

	/**
	 * ファイルを削除
	 * 
	 * @param view
	 */
	@SuppressLint("ShowToast")
	public void deleteFile(View view) {

		// 内部ストレージのファイルを削除
		deleteFile(FILENAME);

		// // 任意のファイルを削除する場合はこっちを使う
		// File file = new File(Environment.getExternalStorageDirectory(), "/" +
		// FILENAME);
		// file.delete();

		// トースト表示
		Toast.makeText(this, "file deleted:" + FILENAME, Toast.LENGTH_LONG)
				.show();
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		// InstanceState へ 入力されたメッセージを保存しておく。
		outState.putString(STATE_ENTERED_MESSAGE, message);
		super.onSaveInstanceState(outState);
	}

	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);
		message = savedInstanceState.getString(STATE_ENTERED_MESSAGE);
	}

	/**
	 * DBへ保存
	 * 
	 * @param view
	 */
	@SuppressLint("ShowToast")
	public void saveToDb(View view) {
		// 入力ボックスの値を取得
		EditText editText = (EditText) findViewById(R.id.edit_message);
		message = editText.getText().toString();

		SQLiteDatabase db = mDbHelper.getWritableDatabase();
		ContentValues values = new ContentValues();
		values.put(MessagesContract.Messages.COLUMN_NAME_MESSAGES_ID,
				String.valueOf(new Date().getTime()));
		values.put(MessagesContract.Messages.COLUMN_NAME_MESSAGE, message);

		long newRowId;
		newRowId = db
				.insert(MessagesContract.Messages.TABLE_NAME, null, values);

		// 新規作成された message_id をトースト表示
		Toast.makeText(this, "saveToDb:" + newRowId, Toast.LENGTH_LONG).show();
	}

	/**
	 * DBから全件読み込み
	 * 
	 * @param view
	 */
	@SuppressLint("ShowToast")
	public void readFromDb(View view) {
		SQLiteDatabase db = mDbHelper.getReadableDatabase();
		String[] projection = { MessagesContract.Messages._ID,
				MessagesContract.Messages.COLUMN_NAME_MESSAGES_ID,
				MessagesContract.Messages.COLUMN_NAME_MESSAGE };
		// 検索条件:カラム名
		String selection = MessagesContract.Messages.COLUMN_NAME_MESSAGE
				+ " LIKE ?";
		// 検索条件:カラム値
		String[] selectionArgs = { "%" };// 全件検索
		// Sort条件
		String sortOrder = MessagesContract.Messages.COLUMN_NAME_MESSAGE
				+ " DESC";

		Cursor c = db.query(MessagesContract.Messages.TABLE_NAME, // The table
																	// to query
				projection, // The columns to return
				selection, // The columns for the WHERE clause
				selectionArgs, // The values for the WHERE clause
				null, // don't group the rows
				null, // don't filter by row groups
				sortOrder // The sort order
				);

		StringBuilder text = new StringBuilder();
		while (c.moveToNext()) {
			text.append("_ID=" + c.getString(0));
			text.append(", messages_id=" + c.getString(1));
			text.append(", message=" + c.getString(2));
			text.append("\n");
		}
		// トースト表示
		Toast.makeText(this, "readFromDb:" + text, Toast.LENGTH_LONG).show();
	}

	/**
	 * DBのデータから "aaa" が含まれるデータを適当に更新
	 * 
	 * @param view
	 */
	@SuppressLint("ShowToast")
	public void updateDb(View view) {
		SQLiteDatabase db = mDbHelper.getReadableDatabase();

		// 更新するデータ
		ContentValues values = new ContentValues();
		values.put(MessagesContract.Messages.COLUMN_NAME_MESSAGE, "hoge");

		// 検索条件
		String selection = MessagesContract.Messages.COLUMN_NAME_MESSAGE
				+ " LIKE ?";
		String[] selectionArgs = { "%aaa%" };

		// 更新 (戻り値は更新件数)
		int count = db.update(MessagesContract.Messages.TABLE_NAME, values,
				selection, selectionArgs);

		// トースト表示
		Toast.makeText(this, "updateDb:" + count, Toast.LENGTH_LONG).show();
	}

	/**
	 * DBから全件削除
	 * 
	 * @param view
	 */
	@SuppressLint("ShowToast")
	public void deleteFromDb(View view) {
		SQLiteDatabase db = mDbHelper.getWritableDatabase();
		// 検索条件:カラム名
		String selection = MessagesContract.Messages.COLUMN_NAME_MESSAGE
				+ " LIKE ?";
		// 検索条件:カラム値
		String[] selectionArgs = { String.valueOf("%") };// 全件検索
		// 削除 (戻り値は削除件数)
		int count = db.delete(MessagesContract.Messages.TABLE_NAME, selection,
				selectionArgs);

		// トースト表示
		Toast.makeText(this, "deleteFromDb:" + count, Toast.LENGTH_LONG).show();
	}

}

ポイントは #saveToDb, #readFromDb, #updateDb, #deleteFromDb メソッド。

DbHelper#getWritableDatabase(またはgetReadableDatabase)でSQLiteDatabaseオブジェクトを取得し、挿入(#insert)したり、選択(#query)したり、更新(#update)したり、削除(#delete)したりすればよい。

SQL実行(query | update | delete)の際に条件を指定したい場合は、selection(条件文)とselectionArgs(条件文の?に入れる値)を第三、第四引数に渡すのがミソ。

posted by 寄り道退屈男 at 13:51 | Comment(0) | TrackBack(0) | Android
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス: [必須入力]

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/64795810
※言及リンクのないトラックバックは受信されません。

この記事へのトラックバック