SQLite を簡単に扱うための DB アダプターの実装

ここでは簡単なメモ入力サンプルプログラムの作成を例にとって、SQLite データベースの利用方法について学びましょう。

着目するところは SQLite を簡単に扱うための DB アダプターの利用です。

SQLite と DB アダプターを利用するサンプルプログラム

それではここで作成するサンプルプログラムの動作を先にみておきましょう。

起動直後はこのとおり。画面下部にテキストの入力フィールドと保存ボタンがあるのみです。

SQLite を簡単に扱うための DB アダプターの実装

テキストを入力して、"Save" ボタンを押すと・・・

SQLite を簡単に扱うための DB アダプターの実装

このように入力文字が保存されます。このときに SQLite データベースに入力文字を保存しています。

SQLite を簡単に扱うための DB アダプターの実装

文字を複数入力すると次の画面のようにリストビューにエントリが増えていきます。

SQLite を簡単に扱うための DB アダプターの実装

エントリを長押しすると、コンテキストメニューが表示されます。今回は削除するかどうかたずねるだけのコンテキストメニューです。

SQLite を簡単に扱うための DB アダプターの実装

"Delete" (削除) を選択すると、本当に削除するかどうか確認するアラートダイアログを表示。そこで Yes を選択すると実際にデータベースから文字を削除します。

SQLite を簡単に扱うための DB アダプターの実装

削除後、リストビューをリフレッシュするとエントリがひとつ消えたことがわかります。

SQLite を簡単に扱うための DB アダプターの実装

さて、これをどのように作ったらよいでしょうか。

データベースの操作以外に UI まわりを片付けてしまいましょう。

レイアウト

main.xml は次の通り。リニアレイアウトで上側 ListView、下側がまたリニアレイアウトで、その中に EditText と Button を横に並べます。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <ListView
    android:layout_height="wrap_content" 
    android:id="@+id/itemListView" 
    android:layout_width="match_parent"
    android:layout_weight="1"/>
  <LinearLayout 
    android:layout_height="wrap_content"
    android:layout_width="match_parent">
    <EditText
      android:layout_height="wrap_content" 
      android:layout_weight="1" 
      android:id="@+id/memoEditText" 
      android:layout_width="wrap_content" 
      android:inputType="textMultiLine">
      <requestFocus/>
    </EditText>
    <Button 
      android:text="Save" 
      android:id="@+id/saveButton"
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content"/>
  </LinearLayout>
</LinearLayout>

ListView の行のレイアウトとなる row.xml は次の通り。単に TextView を上下に文字の大小を変えて並べているだけです。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical">
  <TextView
    android:text="TextView"
    android:id="@+id/noteTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="20sp"
    android:paddingLeft="10dp"
    android:paddingRight="10dp"
    android:paddingTop="10dp"
    android:paddingBottom="0dp"/>
  <TextView
    android:text="TextView"
    android:id="@+id/lastupdateTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="10sp"
    android:paddingLeft="10dp"
    android:paddingRight="10dp"
    android:paddingTop="2dp"
    android:paddingBottom="10dp"/>
</LinearLayout>

ListView の行のカスタマイズとカスタムアダプターについては、「ListView の行のカスタマイズ」 を参照してください。

例に用いるデータ

便宜上ここでは、データベースに格納するデータを表すクラスを定義しておきます。

package com.keicode.android.test;

public class Note {
  protected int id;
  protected String note;
  protected String lastupdate;
  
  public Note(int id, String note, String lastupdate){
    this.id = id;
    this.note = note;
    this.lastupdate = lastupdate;
  }
  
  public String getNote(){
    return note;
  }
  
  public String getLastupdate(){
    return lastupdate;
  }
  
  public int getId(){
    return id;
  }

}

メモ内容と更新日時を保持するフィールドだけ定義しています。

DB アダプター

さて、いよいよ今回のキモの部分です。

SQLite データベースを扱うには次のような、android.database.sqlite.SQLiteOpenHelper をラップするデータベースアダプターを定義すると便利です。

package com.keicode.android.test;

import java.util.Date;

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

public class DBAdapter {

  static final String DATABASE_NAME = "mynote.db";
  static final int DATABASE_VERSION = 1;
  
  public static final String TABLE_NAME = "notes";
  public static final String COL_ID = "_id";
  public static final String COL_NOTE = "note";
  public static final String COL_LASTUPDATE = "lastupdate";

  protected final Context context;
  protected DatabaseHelper dbHelper;
  protected SQLiteDatabase db;
  
  public DBAdapter(Context context){
    this.context = context;
    dbHelper = new DatabaseHelper(this.context);
  }
  
  //
  // SQLiteOpenHelper
  //
  
  private static class DatabaseHelper extends SQLiteOpenHelper {

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

    @Override
    public void onCreate(SQLiteDatabase db) {
      db.execSQL(
        "CREATE TABLE " + TABLE_NAME + " ("
        + COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
        + COL_NOTE + " TEXT NOT NULL,"
        + COL_LASTUPDATE + " TEXT NOT NULL);");
    }

    @Override
    public void onUpgrade(
      SQLiteDatabase db,
      int oldVersion,
      int newVersion) {
      db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
      onCreate(db);
    }
    
  }
    
  //
  // Adapter Methods
  //
  
  public DBAdapter open() {
    db = dbHelper.getWritableDatabase();
    return this;
  }
  
  
  public void close(){
    dbHelper.close();
  }
  

  //
  // App Methods
  //
  
  
  public boolean deleteAllNotes(){
    return db.delete(TABLE_NAME, null, null) > 0;
  }
  
  public boolean deleteNote(int id){
    return db.delete(TABLE_NAME, COL_ID + "=" + id, null) > 0;
  }
  
  public Cursor getAllNotes(){
    return db.query(TABLE_NAME, null, null, null, null, null, null);
  }
  
  public void saveNote(String note){
    Date dateNow = new Date ();
    ContentValues values = new ContentValues();
    values.put(COL_NOTE, note);
    values.put(COL_LASTUPDATE, dateNow.toLocaleString());
    db.insertOrThrow(TABLE_NAME, null, values);
  }
}

static の変数でデータベース名、バージョン、カラム名を定義しています。

SQLLiteOpenHelper の派生クラスを内部クラスで定義します。 このときコンストラクタでデータベース名、 バージョン番号をスーパークラスのコンストラクタに渡しています。

これによって、バージョンが変わったときに onUpgrade が自動的に呼び出されるようになります。

ここでは onUpgrade では元のテーブルをドロップしているだけですが、 必要に応じて新しいデータベースにデータベースを移行するようにすればよいでしょう。

アダプターのメソッドとアプリケーション固有メソッド

この形の DB アダプターでは open と close をこの例のように定義しておくと便利です。

その他、アプリケーション固有のメソッドをいくつか定義すると良いでしょう。

サンプルプログラムの実装

以上のように DB アダプターを定義しておくと、それの利用側は次のように簡略化されます。

package com.keicode.android.test;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.view.View.OnLongClickListener;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class SQLiteTest1Activity extends Activity 
  implements OnClickListener {
  
  static final String TAG = "SQLiteTest1";
  static final int MENUITEM_ID_DELETE = 1;
  ListView itemListView;
  EditText noteEditText;
  Button  saveButton;
  static DBAdapter dbAdapter;
  static NoteListAdapter listAdapter;
  static List<Note> noteList = new ArrayList<Note>();
  
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        findViews();
        setListeners();
                
      dbAdapter = new DBAdapter(this);
      listAdapter = new NoteListAdapter();
      itemListView.setAdapter(listAdapter);

      loadNote();
    }
    
    protected void findViews(){
      itemListView = (ListView)findViewById(R.id.itemListView);
      noteEditText = (EditText)findViewById(R.id.memoEditText);
      saveButton = (Button)findViewById(R.id.saveButton);
    }
    
    protected void loadNote(){
      noteList.clear();
      
      // Read
      dbAdapter.open();
      Cursor c = dbAdapter.getAllNotes();
      
      startManagingCursor(c);
      
      if(c.moveToFirst()){
        do {
          Note note = new Note(
            c.getInt(c.getColumnIndex(DBAdapter.COL_ID)),
            c.getString(c.getColumnIndex(DBAdapter.COL_NOTE)),
            c.getString(c.getColumnIndex(DBAdapter.COL_LASTUPDATE))
            );
          noteList.add(note);
        } while(c.moveToNext());
      }
      
      stopManagingCursor(c);      
      dbAdapter.close();
      
      listAdapter.notifyDataSetChanged();
    }
    
    protected void saveItem(){
      dbAdapter.open();
      dbAdapter.saveNote(noteEditText.getText().toString());
      dbAdapter.close();
      noteEditText.setText("");
      loadNote();
    }
    
    
    protected void setListeners(){
      saveButton.setOnClickListener(this);
      
      itemListView.setOnCreateContextMenuListener(
        new OnCreateContextMenuListener(){
          @Override
          public void onCreateContextMenu(
            ContextMenu menu, 
            View v, 
            ContextMenuInfo menuInfo) {
            menu.add(0, MENUITEM_ID_DELETE, 0, "Delete");
          }
        });
    }
    
    
  @Override
  public boolean onContextItemSelected(MenuItem item) {
  
    switch(item.getItemId()){
    case MENUITEM_ID_DELETE:
      AdapterView.AdapterContextMenuInfo menuInfo
        = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();      

        Note note = noteList.get(menuInfo.position);
        final int noteId = note.getId();
        
      new AlertDialog.Builder(this)
        .setIcon(R.drawable.icon)
        .setTitle("Are you sure you want to delete this note?")
        .setPositiveButton(
          "Yes", 
          new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dbAdapter.open();
                if(dbAdapter.deleteNote(noteId)){
                  Toast.makeText(
                    getBaseContext(), 
                    "The note was successfully deleted.", 
                    Toast.LENGTH_SHORT);
                  loadNote();
                }
                dbAdapter.close();
            }
          })
        .setNegativeButton(
          "Cancel",
          null)
        .show();
      
      return true;
    }
    return super.onContextItemSelected(item);
  }

  
  @Override
  public void onClick(View v) {
    switch(v.getId()){
    case R.id.saveButton:
      saveItem();
      break;
    }
  }
  
  
  private class NoteListAdapter extends BaseAdapter {

    @Override
    public int getCount() {
      return noteList.size();
    }

    @Override
    public Object getItem(int position) {
      return noteList.get(position);
    }

    @Override
    public long getItemId(int position) {
      return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      TextView noteTextView;
      TextView lastupdateTextView;
      View v = convertView;
      if(v==null){
        LayoutInflater inflater = 
          (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        v = inflater.inflate(R.layout.row, null);
      }
      Note note = (Note)getItem(position);
      if(note != null){
        noteTextView = (TextView)v.findViewById(R.id.noteTextView);
        lastupdateTextView = (TextView)v.findViewById(
          R.id.lastupdateTextView);
        noteTextView.setText(note.getNote());
        lastupdateTextView.setText(note.getLastupdate());
        v.setTag(R.id.noteid, note);
      }
      return v;
    }
    
  }
}

メモの保存、削除箇所ではそれぞれ、 saveItem メソッド、 アダプタの deleteNote メソッドを呼び出しています。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Android 開発入門