【Android】SQLiteデータベースに保存する方法|TextView・EditTextで登録・表示・削除

SQLiteにデータを保存するメモアプリのイメージ。TextView・EditText・SQLiteの関係を図で示したアイキャッチ

Android Studioで、SQLiteデータベースにデータを保存し、その結果を画面に表示して確認する方法を紹介します。

Googleは、SQLiteを直接扱う方法よりも、抽象化レイヤーの Room を使う方法を案内しています。

ただ、SQLiteそのものの動きや、データがどのように保存・表示されるのかを理解したいときは、まずシンプルな構成で試してみるのも分かりやすいです。

この記事では、TextView・EditText・Button を使った小さなメモアプリを例にして、次の流れを確認していきます。

  • 入力欄にタイトルと内容を入力する
  • 「登録」ボタンでSQLiteに保存する
  • 保存した内容をTextViewに表示する
  • もう一度登録すると更新される
  • 「削除」ボタンでデータを消す

完成イメージを先に見ておくと、これから何を作るのかつかみやすくなります。

SQLiteを使ったメモアプリの完成イメージ。上に表示エリア、下に入力欄と登録・削除ボタンがある画面

実際の動きは次のようになります。

タイトルと内容を入力して登録・更新・削除する動きを示したアニメーションGIF

動きの流れを図にすると、次のようなイメージです。

EditTextからSQLiteに保存し、TextViewに表示するまでの流れを6ステップで示した図

Android Studioのインストールや基本的な画面の見方は、先にこちらの記事を読んでおくと進めやすいです。

目次

構成

このサンプルの主な構成は、次のようになっています。

MainActivity・activity_main.xml・DBContract・SampDatabaseHelper・SQLiteのつながりを示した構成図

こちらの図でも、ファイルどうしの関係を確認できます。

MainActivity、activity_main.xml、SampDatabaseHelper、SQLite、strings.xml などの構成を示した図

主な役割は次のとおりです。

  • MainActivity:画面表示と処理の中心
  • activity_main.xml:画面レイアウト
  • DBContract:テーブル名やカラム名の定義
  • SampDatabaseHelper:データベース作成と管理
  • SQLite:実際のデータ保存先
  • strings.xml / frame_border.xml:文字列や見た目の補助

順番に見ていきます。

プロジェクト作成

新しいプロジェクトは、前回の記事の「プロジェクト作成」の章と同じ流れで作成できます。

Android Studioをインストール|プロジェクト作成の章

今回のプロジェクト名は、SampTextView とします。

リソース準備

まずは、アプリ内で使う文字列や入力欄の見た目を整えるためのリソースを用意します。

strings.xml

app/res/values/strings.xml を開いて、次のように編集します。

<resources>
    <string name="app_name">SampTextView</string>
    <string name="db_title">データベース登録内容</string>
    <string name="reg_title">登録・編集</string>
    <string name="title">タイトル</string>
    <string name="input_title">タイトルを入力</string>
    <string name="contents">内容</string>
    <string name="input_contents">内容を入力</string>
    <string name="reg">登録</string>
    <string name="del">削除</string>
</resources>

ここで定義した name を、レイアウトやJavaコード側から参照します。

frame_border.xml

次に、入力欄に枠線を付けるためのDrawableリソースを追加します。

app/res/drawable を右クリックして、[New] → [Drawable Resource File] を選び、ファイル名を frame_border にします。

Drawable Resource File を追加して frame_border.xml を作成する画面

作成した frame_border.xml を、次のように編集します。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <stroke  android:width="3px" android:color="#CCCCCC" />
    <padding android:left="2px"  android:top="2px"
             android:right="2px" android:bottom="2px" />
    <corners android:radius="2px" />
</shape>

この設定を使うと、EditTextの入力欄を見やすくできます。

frame_border.xml で設定した入力欄の枠線の見た目

DBContract

次に、テーブル名やカラム名をまとめる DBContract クラスを追加します。

Javaクラスを追加するときは、パッケージを右クリックして [New] → [Java Class] を選びます。

New Java Class を選んで DBContract クラスを追加する画面

クラス名を DBContract にして、次の内容を入力します。

package com.example.samptextview;

import android.provider.BaseColumns;

// データベースのテーブル名・項目名を定義
public final class DBContract {

    // 誤ってインスタンス化しないようにコンストラクタをプライベート宣言
    private DBContract() {}

    // テーブルの内容を定義
    public static class DBEntry implements BaseColumns {
        // BaseColumns インターフェースを実装することで、内部クラスは_IDを継承できる
        public static final String TABLE_NAME           = "samp_tbl";
        public static final String COLUMN_NAME_TITLE    = "title";
        public static final String COLUMN_NAME_CONTENTS = "contents";
        public static final String COLUMN_NAME_UPDATE   = "up_date";
    }
}

BaseColumns を実装しているので、_ID という主キー用の定数を利用できます。

データベース作成|SQLiteOpenHelper

AndroidでSQLiteを使うときは、SQLiteOpenHelper を継承したクラスを作っておくと、データベースの作成や管理がしやすくなります。

全体像は、次の図のようなイメージです。

DBContractとSampDatabaseHelperの役割、テーブル構成、PRIMARY KEY・DEFAULT・TRIGGERの意味をまとめた図

新しいJavaクラスを追加して、名前を SampDatabaseHelper とし、次の内容を入力します。

package com.example.samptextview;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import static com.ma_chanblog.samptextview.DBContract.DBEntry;

// データベースをアプリから使用するために、 SQLiteOpenHelperを継承する
// SQLiteOpenHelperは、データベースやテーブルが存在する場合はそれを開き、存在しない場合は作成してくれる
public class SampDatabaseHelper extends SQLiteOpenHelper {

    // データベースのバージョン
    // テーブルの内容などを変更したら、この数字を変更する
    static final private int VERSION = 2;

    // データベース名
    static final private String DBNAME = "samp.db";

    // コンストラクタは必ず必要
    public SampDatabaseHelper(Context context) {
        super(context, DBNAME, null, VERSION);
    }

    // データベース作成時にテーブルを作成
    public void onCreate(SQLiteDatabase db) {

        // テーブルを作成
        db.execSQL(
            "CREATE TABLE "+ DBEntry.TABLE_NAME + " (" +
            DBEntry._ID + " INTEGER PRIMARY KEY, " +
            DBEntry.COLUMN_NAME_TITLE + " TEXT default 'タイトル', " +
            DBEntry.COLUMN_NAME_CONTENTS + " TEXT default '', " +
            DBEntry.COLUMN_NAME_UPDATE + " INTEGER DEFAULT (datetime(CURRENT_TIMESTAMP,'localtime'))) ");

        // トリガーを作成
        db.execSQL(
            "CREATE TRIGGER trigger_samp_tbl_update AFTER UPDATE ON " + DBEntry.TABLE_NAME +
            " BEGIN "+
            " UPDATE " + DBEntry.TABLE_NAME + " SET up_date = DATETIME('now', 'localtime') WHERE rowid == NEW.rowid; "+
            " END;");
    }

    // データベースをバージョンアップした時、テーブルを削除してから再作成
    public void onUpgrade(SQLiteDatabase db, int i, int i1) {

        db.execSQL("DROP TABLE IF EXISTS " + DBEntry.TABLE_NAME);
        onCreate(db);
    }
}

テーブル作成

onCreate() では、データベースが初めて作られるタイミングでテーブルも作成します。

execSQL() は、SQL文を実行するためのメソッドです。

CREATE TABLE でテーブルを作成し、CREATE TRIGGER で更新日時を自動更新する仕組みを追加しています。

PRIMARY KEY

次の部分では、_ID というINTEGER型の項目を PRIMARY KEY にしています。

DBEntry._ID + " INTEGER PRIMARY KEY, "

PRIMARY KEYは、その行を一意に識別するための項目です。

INTEGER型でPRIMARY KEYを指定すると、登録時に値を省略した場合でも、自動的に重ならない番号が割り振られます。

PRIMARY KEY の役割を説明した図

default

次のように書くと、値が指定されなかったときに既定値が入ります。

DBEntry.COLUMN_NAME_TITLE + " TEXT default 'タイトル', "
DBEntry.COLUMN_NAME_CONTENTS + " TEXT default '', "
DBEntry.COLUMN_NAME_UPDATE + " INTEGER DEFAULT (datetime(CURRENT_TIMESTAMP,'localtime'))) "
  • title は既定値として「タイトル」
  • contents は空文字
  • up_date は登録時点の日時

が入る設定です。

TRIGGER

CREATE TRIGGER ...

の部分は、データが更新されたときに up_date を自動更新するための設定です。

更新日時を自動で入れておきたいときに便利です。

データベースアップグレード

onUpgrade() は、データベースのバージョン番号が変わったときに呼ばれます。

このサンプルでは、古いテーブルを削除してから作り直しています。

そのため、テーブル構成を変更したときは VERSION の値も合わせて見直します。

画面レイアウト

次に、画面レイアウトを設定します。

完成画面の中で、それぞれの部品がどこに当たるかを先に見ておくと分かりやすいです。

activity_main.xmlの主な部品と、TextView・EditText・Button・LinearLayoutの役割を示した図

画面例はこちらです。

データベース登録内容の表示欄と、登録・編集用の入力欄・ボタンがある画面例

activity_main.xml

activity_main.xml を、次のように編集します。

activity_main.xml(クリックして表示)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
 
    <TextView
        android:id="@+id/label_DB"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:layout_marginStart="10dp"
        android:layout_marginTop="20dp"
        android:text="@string/db_title"
        android:textAlignment="textStart"
        android:textSize="24sp" />
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
 
        <TextView
            android:id="@+id/label_tilte"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_marginTop="10dp"
            android:text="@string/title"
            android:textSize="18sp" />
 
        <TextView
            android:id="@+id/textView_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_marginTop="10dp"
            android:textSize="18sp" />
    </LinearLayout>
 
    <TextView
        android:id="@+id/label_contents"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="10dp"
        android:text="@string/contents"
        android:textSize="18sp" />
 
    <TextView
        android:id="@+id/textView_contents"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="10dp"
        android:textSize="18sp" />
 
    <View
        android:id="@+id/divider"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="?android:attr/listDivider" />
 
    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:layout_marginStart="10dp"
        android:layout_marginTop="20dp"
        android:text="@string/reg_title"
        android:textSize="24sp" />
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
 
        <TextView
            android:id="@+id/label_tilte2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_marginTop="10dp"
            android:layout_weight="1"
            android:text="@string/title"
            android:textSize="18sp" />
 
        <EditText
            android:id="@+id/editTitle"
            android:layout_width="300dp"
            android:layout_height="40dp"
            android:layout_marginBottom="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_marginTop="10dp"
            android:background="@drawable/frame_border"
            android:hint="@string/input_title"
            android:importantForAutofill="no"
            android:inputType="text"
            android:singleLine="true" />
    </LinearLayout>
 
    <TextView
        android:id="@+id/label_contents2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:text="@string/contents"
        android:textSize="18sp" />
 
    <EditText
        android:id="@+id/editContents"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:background="@drawable/frame_border"
        android:gravity="top"
        android:hint="@string/input_contents"
        android:importantForAutofill="no"
        android:inputType="textMultiLine|text"
        android:singleLine="true" />
 
    <LinearLayout
        style="?android:attr/buttonBarStyle"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
 
        <Button
            android:id="@+id/btn_reg"
            style="?android:attr/buttonBarStyle"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginBottom="10dp"
            android:layout_marginEnd="10dp"
            android:layout_marginStart="80dp"
            android:layout_marginTop="10dp"
            android:onClick="onSave"
            android:text="@string/reg"
            android:textSize="18sp" />
 
        <Button
            android:id="@+id/btn_del"
            style="?android:attr/buttonBarStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginBottom="10dp"
            android:layout_marginEnd="90dp"
            android:layout_marginStart="30dp"
            android:layout_marginTop="10dp"
            android:layout_weight="1"
            android:onClick="onDelete"
            android:text="@string/del"
            android:textSize="18sp" />
 
    </LinearLayout>
 
</LinearLayout>

このレイアウトでは、LinearLayout を使って部品を縦方向・横方向に並べています。

また、editTitleeditContents の背景に frame_border を指定することで、入力欄の枠線を表示しています。

下のボタンでは、android:onClick を使ってメソッドをひも付けています。

  • 登録ボタン → onSave()
  • 削除ボタン → onDelete()

レイアウトエディタも合わせて使うと、画面のイメージを確認しながら調整しやすくなります。設定できるオブジェクトやパラメータを見ながら進めたいときにも便利です。

androidレイアウトエディタ

アクティビティ(MainActivity)

次に、画面表示とデータ操作をまとめる MainActivity を作成します。

MainActivity (クリックして表示)
package com.example.samptextview;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import static com.ma_chanblog.samptextview.DBContract.DBEntry;

public class MainActivity extends AppCompatActivity {

    private SampDatabaseHelper helper = null;

    private TextView viewTitle    = null;
    private TextView viewContents = null;
    private EditText editTitle    = null;
    private EditText editContents = null;

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

        // ビューオブジェクトを取得
        viewTitle    = findViewById(R.id.textView_title);
        viewContents = findViewById(R.id.textView_contents);
        editTitle    = findViewById(R.id.editTitle);
        editContents = findViewById(R.id.editContents);

        // ヘルパーを準備
        helper = new SampDatabaseHelper(this);

        // データを表示
        onShow();
    }

    // データを表示する
    protected void onShow() {
    
        // データベースから取得する項目を設定
        String[] cols = {DBEntry.COLUMN_NAME_TITLE, DBEntry.COLUMN_NAME_CONTENTS};
        
        // 読み込みモードでデータベースをオープン
        try (SQLiteDatabase db = helper.getReadableDatabase()) {
        
            // データを取得するSQLを実行
            // 取得したデータがCursorオブジェクトに格納される
            Cursor cursor = db.query(DBEntry.TABLE_NAME, cols, null,
                    null, null, null, null, null);
                    
           // moveToFirstで、カーソルを検索結果セットの先頭行に移動
           // 検索結果が0件の場合、falseが返る
            if (cursor.moveToFirst()){
            
                // 表示用のテキスト・コンテンツに検索結果を設定
                viewTitle.setText(cursor.getString(0));
                viewContents.setText(cursor.getString(1));
                
                // 入力用のテキスト・コンテンツにも検索結果を設定
                editTitle.setText(cursor.getString(0));
                editContents.setText(cursor.getString(1));

            } else {
                // 検索結果が0件の場合のメッセージを設定
                viewTitle.setText("データがありません");
                viewContents.setText("");

                editTitle.setText("");
                editContents.setText("");
            }
        }

    }

    // 保存処理 
    public void onSave(View view) {
    
        // 入力欄に入力されたタイトルとコンテンツを取得
        String title    = editTitle.getText().toString();
        String contents = editContents.getText().toString();

        // 書き込みモードでデータベースをオープン
        try (SQLiteDatabase db = helper.getWritableDatabase()) {
        
            // 入力されたタイトルとコンテンツをContentValuesに設定
            // ContentValuesは、項目名と値をセットで保存できるオブジェクト
            ContentValues cv = new ContentValues();
            cv.put(DBEntry.COLUMN_NAME_TITLE, title);
            cv.put(DBEntry.COLUMN_NAME_CONTENTS, contents);

            // 現在テーブルに登録されているデータの_IDを取得
            Cursor cursor = db.query(DBEntry.TABLE_NAME,  new String[] {DBEntry._ID}, null, null,
                                null, null, null, null);

            // テーブルにデータが登録されていれば更新処理
            if (cursor.moveToFirst()){
            
                // 取得した_IDをparamsに設定
                String[] params = {cursor.getString(0)};
                
                // _IDのデータを更新
                db.update(DBEntry.TABLE_NAME, cv, DBEntry._ID + " = ?", params);
                
            } else {
            
                // データがなければ新規登録
                db.insert(DBEntry.TABLE_NAME, null, cv);
            }
        }

        // データを表示
        onShow();

    }

    // 削除処理
    public void onDelete(View view){

        try (SQLiteDatabase db = helper.getWritableDatabase()) {
            db.delete(DBEntry.TABLE_NAME, null, null);
        }

        // データを表示
        onShow();
    }

}

MainActivityの主な処理

onCreate()

onCreate() は、アクティビティが最初に作成されたときに呼ばれるメソッドです。

ここでは、次の準備をしています。

  • setContentView() でレイアウトを読み込む
  • findViewById() で画面部品を取得する
  • SampDatabaseHelper を準備する
  • onShow() を呼んで保存済みデータを表示する

onShow()

onShow() は、データベースからデータを読み込んで画面に表示する処理です。

  • getReadableDatabase() で読み込み用のデータベースを開く
  • query() でタイトルと内容を取得する
  • データがあればTextViewとEditTextにセットする
  • データがなければ「データがありません」と表示する

onSave()

onSave() は、登録ボタンが押されたときの処理です。

最後に onShow() で表示を更新する

EditTextからタイトルと内容を取得する

ContentValues にデータを詰める

すでにデータがあるか確認する

データがあれば update()

なければ insert()

onDelete()

onDelete() は、削除ボタンが押されたときの処理です。

  • delete() でテーブル内のデータを削除する
  • onShow() を呼んで画面表示も更新する

登録・更新・削除の動きは、図にすると次のようになります。

登録時はINSERT、再登録時はUPDATE、削除時はDELETEになる流れを示した図

確認しておきたいところ

データベースの変更が反映されない

テーブル構成を変えたのに反映されないときは、VERSION の値を見直します。

SQLiteOpenHelperは、バージョン番号が変わったときに onUpgrade() を呼ぶ仕組みなので、ここが同じままだと古い構成のまま動くことがあります。

データが表示されない

onShow() が呼ばれているか、findViewById() で取得しているIDがレイアウト側と合っているかを確認します。

入力欄の見た目が崩れる

frame_border.xml の指定漏れや、activity_main.xml 側の android:background="@drawable/frame_border" の指定漏れがないかを見ます。

まとめ

今回は、TextView・EditText・Button・SQLite を使ったシンプルなメモアプリを作りながら、SQLiteの基本的な使い方を確認しました。

流れとしては、次のように考えると分かりやすいです。

  1. EditTextに入力する
  2. MainActivityで値を受け取る
  3. ContentValuesにまとめる
  4. SQLiteに保存する
  5. データを読み出す
  6. TextViewに表示する

Roomのような新しい仕組みを使う前に、SQLiteの基本動作を見ておきたいときにも使いやすい内容です。

こちらは、関連記事です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次