データベースと画面間で必要になった、コード値の変換をDTOクラスに任せました。

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

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

課題が出て、解決案を考えたので、
ケーススタディの一例として、書き留めておきたいと思います。

# 課題
ステータスを表す値の、文字列とBooleanの変換、どこでやる？

## 経緯
1. タスク管理アプリを作成している
1. チェックボックスでTODOを `DOING`から`DONE`へステータスを変えたい！
1. SQLiteのテーブルに `Status`カラムを追加したぜ！
1. SQLiteはBoolean型がないので、コード値の文字列、 `0`, `1`で管理するぜ！
1. あ…AndroidのCheckBox Viewは、Booleanか…
1. テーブルから取得した際…変換しないと…変換して…テーブルに入れないと…
1. あれ…どこで…？ 誰が…？

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

# 目標
いい感じのクラスに、変換のロジックの責務をお願いする

# 結果
[案3](https://qiita.com/naokiur/items/0b98f51e1965db06eaa1#%E6%A1%883-model%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%ABinner-enum%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92%E4%BD%9C%E6%88%90%E3%81%97%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B)を採用、
いい感じのクラスに責務を負わせることができ、
また、読みやすくなったと感じ、目標が達成できたと感じました。


# 現状
[前回](https://qiita.com/naokiur/items/4c0e79a2f6f91012ad22)、こそこそ作成しているタスク管理アプリに、
タスクを追加する機能を実装する、という記事を書きました。

そこでは、 ~~残念なことですが~~ 以下のように、コメントがないと `なんで0を入れているのかがわからないソースコード`になってしまっています。

```AddActivity.kt
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()
    }

}
```

また、登録したタスクを表示する機能とActivity、
CheckBox Viewをクリックした際に、ステータスを更新する機能を、暫定的に作成しました。

```TasksDatabaseHelper.kt
    fun updateCheck(id: Long, status: String): Boolean {
        val db = writableDatabase

        val values = ContentValues()
        values.put(DBContract.TaskEntry.STATUS, status)

        db.update(
                DBContract.TaskEntry.TABLE_NAME,
                values,
                DBContract.TaskEntry.ID + " = ?",
                arrayOf(id.toString())
        )

        return true
    }

```

```MainActivity.kt
class MainActivity : AppCompatActivity() {

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

        val taskDatabaseHelper = TasksDatabaseHelper(this)
        val taskList:ArrayList<TaskModel> = taskDatabaseHelper.selectAll()

        // 暫定的になので、1つめのレコードを、画面に表示する
        if (taskList.size != 0) {
            val  firstTask = taskList[0]

            first_task_name.text = firstTask.name
//            first_status.isChecked = firstTask.status 

            first_status.setOnClickListener { _ ->
//                taskDatabaseHelper.updateCheck(firstTask.id, first_status.isChecked)

            }
        }

```

`Doing`のとき、CheckBox Viewはチェックなし(isChecked == false)、
`Done`のとき、CheckBox Viewはチェックあり(isChecked == true)としたいところですが、
上記の通り、SQLiteはBoolean型がなく、コード値で管理しているため、
当然ながら、 `first_status.isChecked = firstTask.status`は、エラーです。

また、`TasksDatabaseHelper.kt#updateCheck`は、idとstatus(String)を渡すようにした上に、
同様にCheckBox ViewのisCheckedはBooleanなので、
当然ながら、 `first_status.isChecked = firstTask.status`は、エラーです。

# 解決案
## 案1 if文を追加し、変換する
愚直にif文を追加し、変換します。

```MainActivity.kt
        // 暫定的になので、1つめのレコードを、画面に表示する
        if (taskList.size != 0) {
            val  firstTask = taskList[0]

            first_task_name.text = firstTask.name
            first_status.isChecked = firstTask.status == "1" // firstTask.statusが'0'ならfalse, '1'ならtrue 

            // firstTask.statusが'0'ならfalse, '1'ならtrue 
            first_status.setOnClickListener { _ ->

                if (first_status.isChecked) {
                    taskDatabaseHelper.updateCheck(firstTask.id, "1")

                } else {
                    taskDatabaseHelper.updateCheck(firstTask.id, "0")

                }
            }
        }

```

### よくなったところ
* エラーがなくなった。

### アレなところ
* 使用する度に、if分岐を追加しなければならず、ロジックが分散する。
* 相変わらずコメントがないと `なんで0を入れているのかがわからないソースコード`である。
    + 定数化すれば、まだマシになる。
        - けれど、Activityにその定数を持つことになってしまい、複数Activityでステータスを用いる場合、定数が乱立する。
            + グローバル定数的なクラスが必要になる。

タスク管理アプリでは、今のところこの2箇所で済んでいますが、
もっと複雑なアプリの場合、こういった箇所がたくさん出てきてしまうと考えられ、
読みにくい場所が乱立してしまうと思われます。
あんまりよくないですね。

## 案2 Converterクラスを作成し、変換する
TaskModelクラスのStatusのための、StatusConverterクラスを作成し、
変換する責務を負ってもらいます。


```StatusConverter.kt
class StatusConverter {
    companion object {
        private const val DOING_CODE: String = "0"
        private const val DONE_CODE: String = "1"

        fun toCode(isDone: Boolean): String {
            return if (isDone) DONE_CODE else DOING_CODE
        }

        fun toIsDone(code: String): Boolean {
            return code == DONE_CODE
        }
    }
}
```

```MainActivity.kt
        // 暫定的になので、1つめのレコードを、画面に表示する
        if (taskList.size != 0) {
            val  firstTask = taskList[0]

            first_task_name.text = firstTask.name
            first_status.isChecked = StatusConverter.toIsDone(firstTask.status)

            first_status.setOnClickListener { _ ->
                taskDatabaseHelper.updateCheck(firstTask.id, StatusConverter.toCode(first_status.isChecked))
            }
        }

```
### よくなったところ
* ロジックが集約された。

### アレなところ
* TaskModelクラスとStatusConverterクラスの紐付きを、意識しなければならない。
* 定数化したけれど、どっちがtrueでどっちがfalseだっけ、が、ひと目ではわかりにくい。

比較的、MainActivity.kt内の記述量が減ったので、
読みやすくはなったと思います。

ただ、案1の問題と同様ですが、
複雑なアプリの場合、コード値を持つものがたくさん出てくると考えられ、
その際は、Converterクラスが乱立、
紐付きが見えにくくなり、読みにくいソースになってしまうと思われます。

## 案3 DTOであるModelクラスにInner Enumクラスを作成し、変換する。
良さげ、と思っているものです。
個人的に、（Java触ってたときから）Enum大好き人間である、という、ある種趣味全開なものです。

Inner Enumクラスを作成し、
変換する責務を負ってもらいます。

```TaskModel.kt
class TaskModel(val name: String, val status: String, val beginData: Long, val endDate: Long, val updateTime: Long) {
    var id: Long = 0
    constructor(id: Long = 0, name: String, status: String, beginData: Long, endDate: Long, updateTime: Long) : this(name, status, beginData, endDate, updateTime) {
        this.id = id
    }

    enum class Status(val value: String, val isDone: Boolean) {
        DOING("0", false),
        DONE("1", true);

        companion object {
            fun fromValue(value: String): Status {
                return values().first { it.value == value }
            }

            fun fromIsDone(isDone: Boolean): Status {
                return values().first { it.isDone == isDone }
            }
        }
    }
}
```

```MainActivity.kt
        // 暫定的になので、1つめのレコードを、画面に表示する
        if (taskList.size != 0) {
            val  firstTask = taskList[0]

            first_task_name.text = firstTask.name
            first_status.isChecked = TaskModel.Status.fromValue(firstTask.status).isDone

            first_status.setOnClickListener { _ ->
                taskDatabaseHelper.updateCheck(firstTask.id, TaskModel.Status.fromIsDone(first_status.isChecked).value)
            }
        }
```
### よくなったところ
* ロジックが集約された。
* 1クラスに集約されたため、紐付きがわかりやすくなった。
* Enumにしたことにより、コード値とBooleanのマッピングが読みやすくなった。
* `private const val`書かなくてよくなった。
    * 個人的に、`private const val`はあまり乱立させたくないため…。

### アレなところ
* 一旦Enumに変換し、String/Booleanを取得する、というのが、冗長に見えるかもしれない。
    + 個人的には、あんまり感じていないが、人によってあるいは…？

# 今後
より良いソースコードを、見つけていきたいです！