AndroidでSqliteデータベースを操作する - Kotlin編

最近はAndroidアプリをKotlinで作成する機会が増えました。
そこで以前Javaで記載した[Sqliteデータベースの操作方法の記事](https://www.studioks.net/jp-androiddb/)をKotlinで書き直してみました。

環境：API 29、Android Studio 3.5

#データベース定義とテーブル作成
まずSQLiteOpenHelperを継承したクラスを作成しデータベースを定義します。

```kotlin
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper

---------- (中略) ----------

private class SampleDBHelper(context: Context, databaseName:String, factory: SQLiteDatabase.CursorFactory?, version: Int) : SQLiteOpenHelper(context, databaseName, factory, version) {

    override fun onCreate(database: SQLiteDatabase?) {
        database?.execSQL("create table if not exists SampleTable (id text primary key, name text, type integer, image BLOB)");
    }

    override fun onUpgrade(database: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        if (oldVersion < newVersion) {
            database?.execSQL("alter table SampleTable add column deleteFlag integer default 0")
        }
    }
}
```
SQLiteOpenHelperは引数にContext、データベース名、[SQLiteDatabase.CursorFactory](https://qiita.com/Yuki_312/items/bd4959926a9d5dd7b049)、バージョン番号を持ちます。
そして以下の2つのイベントをoverrideする必要があります。

・onCreate
データベースが初期作成された時に発生するイベントです。
このサンプルでは以下のフィールドを持つSampleTableというテーブルを作成するCREATE TABLE文を実行しています。

| フィールド名 | データ型 |補足 |
|:-----------|------------:|:------------:|
| id       | 文字列型        | 主キー         |
| name       | 文字列型        |         |
| type       | 数値型        |          |
| image       | BLOB型        |          |
最後のBLOB型はImageViewに表示しているBitmapをバイト配列にしてSqliteのテーブルに保存する為のデータ型です。

・onUpgrade
SQLiteOpenHelperに渡されるバージョン番号が変わった時に発生するイベントです。
このサンプルではSampleTableに以下のフィールドを追加するALTER TABLE文を実行しています。

| フィールド名 | データ型 |補足 |
|:-----------|------------:|:------------:|
| deleteFlag | 数値型        | 初期値：0         |

#データベースへのアクセス
続いてデータベースへのアクセス方法をご紹介します。

まず最初に作成したSQLLiteOpenHelperを継承したクラスを呼び出します。

```kotlin
val dbHelper = SampleDBHelper(applicationContext, "SampleDB", null, 1);
```

そしてSQLLiteOpenHelperからデータベースを操作する為のSQLiteDatabaseを取得します。
この時、SELECTのみを行いたい場合はreadableDatabaseメソッドを実行し、読み取り専用のSQLiteDatabaseを取得します。

```kotlin
val database = dbHelper.readableDatabase
```

INSERT、UPDATE、DELETEを行いたい場合はwritableDatabaseメソッドを実行し、書き込み可能なSQLiteDatabaseを取得します。

```kotlin
val database = dbHelper.writableDatabase
```

では実際にSampleTableに対してINSERT、UPDATE、DELETE、SELECTを実行してみましょう。

#INSERT
```kotlin
import android.app.Activity
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import java.io.ByteArrayOutputStream

class MainActivity : AppCompatActivity() {
    private val dbName: String = "SampleDB"
    private val tableName: String = "SampleTable"
    private val dbVersion: Int = 1

---------- (中略) ----------

    private fun insertData(id: String, name: String, type: Int, bitmap: Bitmap) {
        try {
            val dbHelper = SampleDBHelper(applicationContext, dbName, null, dbVersion);
            val database = dbHelper.writableDatabase

            val values = ContentValues()
            values.put("id", id)
            values.put("name", name)
            values.put("type", type)
            val byteArrayOutputStream = ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
            val bytes = byteArrayOutputStream.toByteArray()
            values.put("image", bytes)

            database.insertOrThrow(tableName, null, values)
        }catch(exception: Exception) {
            Log.e("insertData", exception.toString())
        }
    }
}
```
このサンプルではSampleTableに対してINSERTを実行しています。
INSERTをする際にはSQLiteDatabaseのinsertOrThrowメソッドを実行します。SQLiteDatabaseにはinsertメソッドもあるのですが、insertメソッドはINSERTに失敗した時にエラーハンドリングができない為、使用しない方がいいでしょう。
INSERTする値はContentValuesにセットしてinsertOrThrowメソッドに渡します。


#UPDATE
```kotlin
import android.app.Activity
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import java.io.ByteArrayOutputStream

class MainActivity : AppCompatActivity() {
    private val dbName: String = "SampleDB"
    private val tableName: String = "SampleTable"
    private val dbVersion: Int = 1

---------- (中略) ----------

    private fun updateData(whereId: String, newName: String, newType: Int, newBitmap: Bitmap) {
        try {
            val dbHelper = SampleDBHelper(applicationContext, dbName, null, dbVersion);
            val database = dbHelper.writableDatabase

            val values = ContentValues()
            values.put("name", newName)
            values.put("type", newType)
            val byteArrayOutputStream = ByteArrayOutputStream();
            newBitmap?.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
            val bytes = byteArrayOutputStream.toByteArray()
            values.put("image", bytes)

            val whereClauses = "id = ?"
            val whereArgs = arrayOf(whereId)
            database.update(tableName, values, whereClauses, whereArgs)
        }catch(exception: Exception) {
            Log.e("updateData", exception.toString())
        }
    }
}
```
このサンプルではidをWhere句に指定して、該当idのname、type、imageの値を更新しています。
UPDATEを行う際にはSQLiteDatabaseのupdateメソッドを使用します。
まずUPDATEする値はINSERTの時と同様にContentValuesに設定します。
次にWHERE句を以下の様に指定します。

・WHERE句の構文をString型で設定

```kotlin
val whereClauses = "id = ?"
```
WHERE句の条件が複数ある場合は"and"もしくは"or"でつなげることができます。

・WHERE句の値をString[]型で設定

```kotlin
val whereArgs = arrayOf(whereId)
```
上記のWhereClausesで指定した順にString配列に値を設定します。

#DELETE
```kotlin
import android.app.Activity
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.graphics.drawable.BitmapDrawable
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log

class MainActivity : AppCompatActivity() {
    private val dbName: String = "SampleDB"
    private val tableName: String = "SampleTable"
    private val dbVersion: Int = 1

---------- (中略) ----------

    private fun deleteData(whereId: String) {
        try {
            val dbHelper = SampleDBHelper(applicationContext, dbName, null, dbVersion);
            val database = dbHelper.writableDatabase

            val whereClauses = "id = ?"
            val whereArgs = arrayOf(whereId)
            database.delete(tableName, whereClauses, whereArgs)
        }catch(exception: Exception) {
            Log.e("deleteData", exception.toString())
        }
    }
}
```
このサンプルではidをWhere句に指定して、該当idのレコードを削除しています。
DELETEを行う際にはSQLiteDatabaseのdeleteメソッドを使用します。
UPDATEと同様にWHERE句の値を指定して実行します。

#SELECT
```kotlin
import android.app.Activity
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import java.io.ByteArrayOutputStream

class MainActivity : AppCompatActivity() {
    private val dbName: String = "SampleDB"
    private val tableName: String = "SampleTable"
    private val dbVersion: Int = 1
    private var arrayListId: ArrayList<String> = arrayListOf()
    private var arrayListName: ArrayList<String> = arrayListOf()
    private var arrayListType: ArrayList<Int> = arrayListOf()
    private var arrayListBitmap: ArrayList<Bitmap> = arrayListOf()

---------- (中略) ----------

    private fun selectData() {
        try {
            arrayListId.clear();arrayListName.clear();arrayListType.clear();arrayListBitmap.clear()

            val dbHelper = SampleDBHelper(applicationContext, dbName, null, dbVersion)
            val database = dbHelper.readableDatabase

            val sql = "select id, name, type, image from " + tableName + " where type in (1, 2) order by id"

            val cursor = database.rawQuery(sql, null)
            if (cursor.count > 0) {
                cursor.moveToFirst()
                while (!cursor.isAfterLast) {
                    arrayListId.add(cursor.getString(0))
                    arrayListName.add(cursor.getString(1))
                    arrayListType.add(cursor.getInt(2))
                    val blob: ByteArray = cursor.getBlob(3)
                    val bitmap = BitmapFactory.decodeByteArray(blob, 0, blob.size)
                    arrayListBitmap.add(bitmap)
                    cursor.moveToNext()
                }
            }
        }catch(exception: Exception) {
            Log.e("selectData", exception.toString());
        }
    }
}
```
SELECTを行う際にはSQLiteDatabaseのrawQueryメソッドを使用し、その引数にSELECTを行うSQL文を設定します。
rawQueryメソッド自体はその名の通り生のSQL文を実行するメソッドなので、SELECT文以外でも使えます。
SQLの実行結果はCursorクラスで受け取ることができますので、moveToFirstメソッドで最初のレコードに移動し、最後のレコードに到達するまでmoveToNextメソッドを繰り返して全レコードの値を取得します。

#サンプルプログラム
上記のコードを使用したサンプルプログラムをGitHubに公開しています。

https://github.com/naosekig/KotlinSqliteSample

#参考文献
[android.database.sqlite | Android Developers](https://developer.android.com/reference/android/database/sqlite/package-summary)
[Android Sqliteプログラミング - Studio K's](https://www.studioks.net/jp-androiddb/)