KotlinでAndroidアプリのデータベースアクセスをしました。

おはようございます。@naokiurです。

以前から興味があった、KotlinでAndroid開発を、
こっそりやっています。

よくある、 `SQLiteOpenHelper`を用いたデータベースアクセスを実装することができたので、
その内容を書き留めておきたいと思います。

# 環境
* MacBook Pro (Retina 13-inch、Early 2015)
* macOS High Sierra Capitan 10.13.2
* Kotlin 1.2
* Android Studio 3.1.2

# 目標
* 画面に入力したタスク情報を、データベースに登録することができる。
    + よくあるタスク管理アプリ

# 実施したこと
* 管理したいタスクの情報を決める
* Contractクラスを作成する
* Modelクラスを作成する
* DatabaseHelperクラスを作成する
* 画面に入力された情報を、DatabaseHelperクラスを用いて登録する、Activityクラスを作成する

## 管理したいタスクの情報を決める
シンプルに、1タスクは、以下のような情報を管理することとしました。

* タスク名
* ステータス
    - Doing(コード値: 0)
    - Done(コード値: 1)
* 開始日
* 終了日
* 更新時刻


## Contractクラスを作成する
データベーススキーマなどを定義する、Contractクラスを作成します。
今回は、1テーブル分なので効果は出ませんが、
データベース全体のテーブルの情報を、テーブル単位のインナークラスとして持っておくのが良い方法、
と書いてあるように見受けられました。（違ったらごめんなさい）

> A good way to organize a contract class is to put definitions that are global to your whole database in the root level of the class.
>  Then create an inner class for each table. Each inner class enumerates the corresponding table's columns.

管理したいタスクの情報を元に、
以下のようなContractクラスを作成しました。

```DBContract.kt
package jp.ne.naokiur.todomanagement.strage

import android.provider.BaseColumns

object DBContract {

    class TaskEntry : BaseColumns {
        companion object {
            const val TABLE_NAME = "tasks"
            const val TASK_NAME = "task_name"
            const val STATUS = "status"
            const val BEGIN_DATE = "begin_date"
            const val END_DATE = "end_date"
            const val UPDATE_TIME = "update_time"
        }
    }
}
```

## Modelクラスを作成する
タスクデータを持ち運ぶためのModelクラスを作成します。
いわゆるDTOという理解をしています。

Contractクラスの定義を元に、
以下のようなModelクラスを作成しました。

```TaskModel.kt
package jp.ne.naokiur.todomanagement.models

class TaskModel(val name: String, val status: String, val beginData: Long, val endDate: Long, val updateTime: Long)

```


## DatabaseHelperクラスを作成する
データベースへアクセスするDatabaseHelperクラスを作成します。

ライブラリなどはあるのですが、今回はオーソドックスに、
SQLiteOpenHelperクラスを継承して作成します。

データベースの作成、およびINSERTができるように、
以下のようなDatabaseHelperクラスを作成しました。

```TasksDatabaseHelper.kt
package jp.ne.naokiur.todomanagement.strage

import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteException
import android.database.sqlite.SQLiteOpenHelper
import jp.ne.naokiur.todomanagement.models.TaskModel

class TasksDatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
    @Throws(SQLiteException::class)
    fun insertTask(task: TaskModel): Boolean {
        val db = writableDatabase

        val values = ContentValues()
        values.put(DBContract.TaskEntry.TASK_NAME, task.name)
        values.put(DBContract.TaskEntry.STATUS, task.status)
        values.put(DBContract.TaskEntry.BEGIN_DATE, task.beginData)
        values.put(DBContract.TaskEntry.END_DATE, task.endDate)
        values.put(DBContract.TaskEntry.UPDATE_TIME, task.updateTime)

        val rowId = db.insert(DBContract.TaskEntry.TABLE_NAME, null, values)

        return true
    }

    override fun onCreate(db: SQLiteDatabase?) {
        db?.execSQL(SQL_CREATE_ENTRIES)
    }

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        db?.execSQL(SQL_DELETE_ENTRIES)
        db?.execSQL(SQL_CREATE_ENTRIES)
    }

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

    companion object {
        const val DATABASE_VERSION = 1
        const val DATABASE_NAME = "TASKS.db"

        private const val SQL_CREATE_ENTRIES =
                "CREATE TABLE " + DBContract.TaskEntry.TABLE_NAME + " (" +
                        DBContract.TaskEntry.TASK_NAME + " TEXT ," +
                        DBContract.TaskEntry.STATUS + " TEXT," +
                        DBContract.TaskEntry.BEGIN_DATE + " INTEGER," +
                        DBContract.TaskEntry.END_DATE + " INTEGER," +
                        DBContract.TaskEntry.UPDATE_TIME + " INTEGER)"

        private const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + DBContract.TaskEntry.TABLE_NAME
    }
} 

```

## 画面に入力された情報を、DatabaseHelperクラスを用いて登録する、Activityクラスを作成する
シンプルに、ボタンが押下された場合、
入力された値をデータベースに登録するActivityを作成します。

```AddActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)
         val taskDatabaseHelper = TasksDatabaseHelper(this)
         val cal = Calendar.getInstance()
         val format = "yyyy/MM/dd"
         val dateFormat = SimpleDateFormat(format, Locale.JAPANESE)

         val beginDateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth ->
            cal.set(Calendar.YEAR, year)
            cal.set(Calendar.MONTH, month)
            cal.set(Calendar.DAY_OF_MONTH, dayOfMonth)

            begin_date!!.text = dateFormat.format(cal.time)
         }

         val limitDateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth ->
            cal.set(Calendar.YEAR, year)
            cal.set(Calendar.MONTH, month)
            cal.set(Calendar.DAY_OF_MONTH, dayOfMonth)

            limit_date!!.text = dateFormat.format(cal.time)
         }
         
         val beginDate = dateFormat.parse(begin_date.text.toString()).time
         val limitDate = dateFormat.parse(limit_date.text.toString()).time

         save_button.setOnClickListener { view ->
             val task = TaskModel(
                    task_name.text.toString(),
                     "0", // 初期値を `Doing`とする
                    beginDate,
                    limitDate,
                    Calendar.getInstance().timeInMillis
             )
             val result = taskDatabaseHelper.insertTask(task)
 
             if (result) {
                 Toast.makeText(this, "new Task Added!", LENGTH_LONG).show()
             }
 
         }
```

# 結果
画面に入力した値が、データベースに登録されていることを確認することができました。
（エミュレータ、DB Browser for SQliteで確認しました。）

<img width="177" alt="スクリーンショット 2018-06-01 9.10.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/129313/abcbc1e8-d052-20f8-a80a-937ee635ea55.png">

<img width="516" alt="スクリーンショット 2018-06-01 9.10.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/129313/807fa96e-7cbc-42f4-5f94-8d8303240668.png">


# 今後
* デザインががたがたなので調整する…
* 画面上で現在のタスクを確認できるようにする
* Doing/Doneはチェックボックスでやるつもりなので、Booleanに変換できるようにする

# 参考にさせていただきました。
* https://developer.android.com/training/data-storage/sqlite
* https://www.tutorialkart.com/kotlin-android/android-sqlite-example-application/