【Androidアプリ開発】データベースを作成/登録/呼出する

2021年4月10日

こんにちは、ノムノムです。

前回は画面レイアウトを行いました。

【Androidアプリ開発】画面レイアウトを編集する
【Androidアプリ開発】画面レイアウトを編集する
前回は新しいアクティビティを作成し遷移することやりました。 今回はTodoを登録する画面のレイアウトを編集して…
http://nomux2.net/post-2206/

今回は、構築レイアウトを利用してデータベースの作成・登録・呼出を説明していこうと思います。

データベースの作成・登録・呼出

最初に作成したデータベース用のクラスを表示します。

package com.example.todomanager

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.graphics.Color

class DatabaseHelper(context: Context): SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {

    var errMsg = ""

    //クラス内のprivate定数を宣言するためにcompanion objectブロックを記述
    companion object {
        //データベースファイル名の定数フィールド
        private const val DATABASE_NAME = "todomanager.db"
        //バージョン情報
        private const val DATABASE_VERSION = 1
    }

    override fun onCreate(db: SQLiteDatabase) {
        //テーブル作成用SQL文字列を作成
        val sb = StringBuilder()

        //Todoを登録するテーブル
        sb.append("CREATE TABLE todo_list (")
        sb.append("id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , ")
        sb.append("title VARCHAR(255), ")
        sb.append("description TEXT, ")
        sb.append("category_id INTEGER, ")
        sb.append("limit_date DATE, ")
        sb.append("limit_time TIME, ")
        sb.append("alarm BIT")
        sb.append(");")

        //SQLの実行
        val sqlTodo = sb.toString()

        //SQLの実行
        db.execSQL(sqlTodo)

        //カテゴリーを登録するテーブル
        sb.clear()
        sb.append("CREATE TABLE categories (")
        sb.append("id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , ")
        sb.append("name VARCHAR(255), ")
        sb.append("color INTEGER ")
        sb.append(");")

        //SQLの実行
        val sqlCategory = sb.toString()

        //SQLの実行
        db.execSQL(sqlCategory)

        //カテゴリー指定なしの行を追加
        sb.clear()
        sb.append("INSERT INTO categories ")
        sb.append("(name, color) values (")
        sb.append(" ")
        sb.append("'指定なし', ")
        sb.append(Color.WHITE.toString())
        sb.append(");")

        //SQLの実行
        val sqlIns = sb.toString()

        //SQLの実行
        db.execSQL(sqlIns)

    }

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { }

    fun getTodoList() : List<String> {

        var listTodo = mutableListOf<String>()

        //データベース接続オブジェクトを取得
        val db = this.writableDatabase

        //検索SQLを作成
        val sql = "SELECT title FROM todo_list ORDER BY _id;"

        //SQLの実行
        val cursor = db.rawQuery(sql, null)

        //カテゴリーのリストを作成
        var todoTitle = ""

        while(cursor.moveToNext()) {
            //IDの列インデックスを取得
            val idxID = cursor.getColumnIndex("title")

            //IDの値を取得
            todoTitle = cursor.getString(idxID)

            //IDを確保
            listTodo.add(todoTitle);

        }

        return listTodo

    }

    fun getCategories() : List<Pair<Int, String>> {
        //データベース接続オブジェクトを取得
        val db = this.writableDatabase

        //検索SQLを作成
        val sql = "SELECT _id, name FROM categories ORDER BY _id;"

        //SQLの実行
        val cursor = db.rawQuery(sql, null)

        //カテゴリーのリストを作成
        var listCategory = mutableListOf<Pair<Int, String>>()

        var id = -1
        var category = ""

        while(cursor.moveToNext()) {
            //IDの列インデックスを取得
            val idxID = cursor.getColumnIndex("_id")

            //IDの値を取得
            id = cursor.getInt(idxID)

            //nameの列インデックスを取得
            val idxName = cursor.getColumnIndex("name")

            //nameの値を取得
            category = cursor.getString(idxName)

            //nameをセットする
            listCategory.add(Pair(id, category))
        }

        return listCategory
    }

    fun addTodo(title :String, description : String, category : Int, limitDate : String, limitTime : String, alarm : Boolean) : Boolean
    {
        try {

            //実行前にエラーチェック
            if (!todoErrCheck(title, description, category, limitDate, limitTime, alarm)) {
                return false
            }

            //データベース接続オブジェクトを取得
            val db = this.writableDatabase

            //Insert用のSQLを作成
            val sqlInsert = "INSERT INTO todo_list (title, description, category_id, limit_date, limit_time, alert) VALUES (?, ?, ?, ?, ?, ?)"

            //SQL文字列を元にプリペアドステートメントを取得
            var stmt = db.compileStatement(sqlInsert)
            //タイトル
            stmt.bindString(1, title)
            //詳細
            stmt.bindString(2, description)
            //カテゴリー
            stmt.bindLong(3, category.toLong())
            //期限日
            if (limitDate.isEmpty()) {
                stmt.bindNull(4)
            } else {
                stmt.bindString(4, limitDate)
            }
            //期限時間
            if (limitTime.isEmpty()) {
                stmt.bindNull(5)
            } else {
                stmt.bindString(5, limitTime)
            }
            //アラーム
            if (alarm) {
                stmt.bindLong(6, 1)
            } else {
                stmt.bindLong(6, 0)
            }

            //SQLの実行
            stmt.executeInsert()

        } catch (e: Exception) {
            //デバッグ出力
            print(e)
            errMsg = "更新に失敗しました。"
            return false
        }

        return true
    }

    //エラーチェック
    private fun todoErrCheck(title :String, description : String, category : Int, limitDate : String, limitTime : String, alarm : Boolean) : Boolean
    {
        //タイトル
        if (title.isNullOrBlank())
        {
            errMsg = String.format("%sが入力されていません", "タイトル")
            return false
        }

        return true
    }
}

メインの部分です。

class MainActivity : AppCompatActivity() {

    private val _helper = DatabaseHelper(this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //データをロードする
        loadTodoList()

    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
       ・・・
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
      ・・・
    }

    override fun onDestroy() {
        //ヘルパーオブジェクトの開放
        _helper.close()

        super.onDestroy()
    }

    //データを取得する関数
    private fun loadTodoList() {

        //Todoリストを取得する
        val lvTodoList = findViewById<ListView>(R.id.lvTodoList)

        //データベースよりTODOのリストを取得する
        val listTodo = _helper.getTodoList()

        //アダプタオブジェクト
        val adapter = ArrayAdapter(applicationContext, android.R.layout.simple_list_item_1, listTodo)

        //リストビューにアダプタオブジェクトを設定
        lvTodoList.adapter = adapter
    }
}

それでは説明します。

SQLiteOpenHelperの継承クラスを作成

今回は「SQLiteOpenHelper」を利用してデータベースにアクセスしています。
まず「java」フォルダのメインソースのあるフォルダを右クリックし「New」-「Kotlin File/Class」を選択します。

Nameを「DatabaseHelper」としKindを「Class」にします。

クラスが作成されたら「SQLiteOpenHelper」を継承します。

import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper

class DatabaseHelper(context: Context): SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
    
    //クラス内のprivate定数を宣言するためにcompanion objectブロックを記述
    companion object {
        //データベースファイル名の定数フィールド
        private const val DATABASE_NAME = "todomanager.db"
        //バージョン情報
        private const val DATABASE_VERSION = 1
    }

    override fun onCreate(db: SQLiteDatabase) { }

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { }
}

この場合、データベースの名称が「todomanager.db」、バージョンが「1」になります。
onCreateは初回インストール時に呼ばれ、onUpgradeはバージョンが更新されたときに呼ばれます。

データベースの作成

続いてデータベースを作成します。SQL文はデータベース言語で文字列で作成します。
データベースを作成するSQL文は以下です。

CREATE TABLE 「テーブル名」 (項目名 型, 項目名 型, ・・・);

SQLについての詳しい説明は割愛します。

ここではtodoを登録するテーブルを作成しています。テーブル作成用のSQL文を作成し実行します。

override fun onCreate(db: SQLiteDatabase) {

    //テーブル作成用SQL文字列を作成
    val sb = StringBuilder()

    //Todoを登録するテーブル
    sb.append("CREATE TABLE todo_list (")
    sb.append("id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , ")
    sb.append("title VARCHAR(255), ")
    sb.append("description TEXT, ")
    sb.append("category_id INTEGER, ")
    sb.append("limit_date DATE, ")
    sb.append("limit_time TIME, ")
    sb.append("alarm BIT")
    sb.append(");")

    //SQLの実行
    val sqlTodo = sb.toString()

    //SQLの実行
    db.execSQL(sqlTodo)
}

「AUTOINCREMENT」は自動採番、「PRIMARY KEY」は主キーです。「NOT NULL」はnull値を許可しないということです。

データベースへのデータの挿入

データベースのデータの挿入もSQLを作成し実行します。

テーブルを挿入するSQL文は以下のようになります。

INSERT INTO 「テーブル名」 (項目名, 項目名, ・・・) VALUES (値, 値, ・・・);


ここでは、カテゴリーのデフォルト項目「指定なし」を挿入するSQL文を確認します。

//カテゴリー指定なしの行を追加
sb.clear()
sb.append("INSERT INTO categories ")
sb.append("(_id, name, color) values (")
sb.append("0, ")
sb.append("'指定なし', ")
sb.append(Color.WHITE.toString())
sb.append(");")

//SQLの実行
val sqlIns = sb.toString()

//SQLの実行
db.execSQL(sqlIns)

データの呼び出し

データの呼び出しも同様にSQLを作成します。
テーブルからデータを呼び出すSQLは以下です。

SELECT 項目名, 項目名, ・・・ FROM 「テーブル名」 (WHERE 条件文) (ORDER BY 項目名,項目名・・・)

取得条件を表すときはWHERE、並び順はORDER BYです。
他にもありますが、ここでは割愛します。

rawQueryメソッドで呼び出した実行結果がCursorオブジェクトに格納されます。
先頭の行からデータを取得し、moveToNextメソッドで次の行に移動します。次の行に移動できればtrue、最終の行の場合falseを返します。

以下の例は取得したTODOのタイトルをListに格納して返す関数です。

    fun getTodoList() : List<String> {

        var listTodo = mutableListOf<String>()

        //データベース接続オブジェクトを取得
        val db = this.writableDatabase

        //検索SQLを作成
        val sql = "SELECT title FROM todo_list ORDER BY _id;"

        //SQLの実行
        val cursor = db.rawQuery(sql, null)

        //カテゴリーのリストを作成
        var todoTitle = ""

        while(cursor.moveToNext()) {
            //IDの列インデックスを取得
            val idxID = cursor.getColumnIndex("title")

            //IDの値を取得
            todoTitle = cursor.getString(idxID)

            //IDを確保
            listTodo.add(todoTitle);

        }

        return listTodo
    }

まとめ

データベースの作成、登録、呼出の説明を書きました。読み直してみるとなんと読みにくいことか・・・

どうしたらもっと読みやすい記事を掛けるようになるのかまだまだ勉強が必要だと痛感しております。

ここまで読んで頂きありがとうございます。