LoginSignup
2
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-06-09

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

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

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

課題

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

経緯

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

環境

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

目標

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

結果

案3を採用、
いい感じのクラスに責務を負わせることができ、
また、読みやすくなったと感じ、目標が達成できたと感じました。

現状

前回、こそこそ作成しているタスク管理アプリに、
タスクを追加する機能を実装する、という記事を書きました。

そこでは、 残念なことですが 以下のように、コメントがないと なんで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を取得する、というのが、冗長に見えるかもしれない。
    • 個人的には、あんまり感じていないが、人によってあるいは…?

今後

より良いソースコードを、見つけていきたいです!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1