Genta Hirauchi

公開日:2020/04/20
更新日:2020/08/03

【Kotlin MVP】MVPアーキテクチャで、TODOアプリを作成する #3

アプリ開発におけるアーキテクチャ(設計)の1つに、MVPというものがあります。

本記事は、ToDoアプリの作成を通して、MVPについて学習するシリーズの第3弾です。

第3弾の今回は、Roomライブラリを使用した、データベースへのアクセス処理を実装します。

なお、本シリーズで実装するTODOアプリのソースコードは、todo-sample | GitHubにて公開しております。

目次

Databaseの実装

ライブラリの導入

まずは、build.gradleを修正し、Roomライブラリを使用できるようにします。

  • build.gradle
(省略)

apply plugin: 'kotlin-kapt'

(省略)

dependencies {
    (省略)
    implementation "androidx.room:room-runtime:2.2.5"
    kapt "androidx.room:room-compiler:2.2.5"
}

Entityの実装

続いて、Entityを実装します。Entityは、データベースのテーブルを表すクラスのことです。

前回の記事で、Taskというデータクラスを作成しました。このTaskを、Entityクラスとして使用します。

  • Task.kt
@Entity
data class Task(
        @PrimaryKey(autoGenerate = true)
        val id: Int,

        @ColumnInfo(name = "state")
        var state: Int,

        @ColumnInfo(name = "description")
        var description: String
)

@Entityをつける事で、Entityクラスとなります。各パラメータが、テーブルのカラムとなります。

@PrimaryKeyがついたパラメータは、主キーとなります。また、autoGenerate = trueとすると、idが自動生成されます。

@ColumnInfoは、カラム名を指定する際に使用します。@ColumnInfoを指定しなかった場合、パラメータ名がカラム名となります。

Daoの実装

続いて、Daoを実装します。Daoは、Data Access Objectsの略称で、データベースにアクセスするためのメソッドを定義するクラスです。

  • TaskDao.kt
@Dao
interface TaskDao {
    @Query("SELECT * FROM task")
    fun getAll(): List<Task>

    @Insert
    fun insert(task: Task)

    @Update
    fun update(task: Task)

    @Delete
    fun delete(task: Task)
}

@Daoをつける事で、Daoクラスとなります。

@Queryは、SQLiteのQueryを実行する際に使用するアノテーションです。

データの追加、更新、削除には、それぞれ@Insert@Update@Deleteというアノテーションを使用することができます。

参考:Dao | Android デベロッパー

Databaseの実装

最後に、Databaseクラスを作成します。Databaseクラスは、RoomDatabaseを継承させて実装します。

  • TaskDatabase.kt
@Database(entities = [Task::class], version = 1)
abstract class TaskDatabase : RoomDatabase() {

    abstract fun taskDao(): TaskDao

    companion object {

        private var INSTANCE: TaskDatabase? = null

        private val lock = Any()

        fun getInstance(context: Context): TaskDatabase {
            synchronized(lock) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.applicationContext,
                        TaskDatabase::class.java, "Task.db")
                        .allowMainThreadQueries()
                        .build()
                }
                return INSTANCE!!
            }
        }
    }
}

@Databaseをつける事で、Databaseクラスとなります。entitiesには、データベースのEntityを配列で指定します。今回はTaskを指定しております。versionには、データベースのバージョンを指定します。

他クラスでDatabaseの作成を行う際は、getInstanceを呼び出すようにします。まだDatabaseが作られていない場合のみ、新規作成されるようにします。

この章の修正内容はDatabaseの実装 | GitHubからも確認頂けます。

Databaseへのアクセス

ここからは、先ほど実装したクラスを呼び出し、MainFragmentからDatabaseへアクセスする処理を実装します。

なお、現段階では、View → Databaseの流れで実装しますが、最終的には、View → Presenter → Repository(Model) → Databaseという流れに修正していきます。

データの取得

まずは、データの取得処理を実装します。

  • MainFragment.kt
class MainFragment : Fragment() {

    private lateinit var dao: TaskDao
    private lateinit var adapter: TasksAdapter

    (省略)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val context = context?: return

        val db = TaskDatabase.getInstance(context)
        dao = db.taskDao()

        val tasks = dao.getAll()

        adapter = TasksAdapter(tasks, itemListener)
        listView.adapter = adapter

        (省略)
    }

    (省略)
}

TaskDatabase.getInstance(context)で、TaskDatabaseを取得しております。

そして、取得したTaskDatabaseのtaskDao()を呼び、TaskDaoを取得しております。

最後に、TaskDaoのgetAll()を呼び、DatabaseからTask一覧を取得しております。

データの追加

続いて、データの追加処理を実装します。

  • MainFragment.kt
class MainFragment : Fragment() {

    (省略)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        (省略)

        addTaskButton.setOnClickListener {
            val newTask = Task(0, 0, "New Task")
            dao.insert(newTask)

            adapter.tasks = dao.getAll()
        }
    }

    (省略)

    private class TasksAdapter(tasks: List<Task>, private val listener: TaskItemListener): BaseAdapter() {

        var tasks: List<Task> = tasks
            set(tasks) {
                field = tasks
                notifyDataSetChanged()
            }

        (省略)
    }

    (省略)
}

まず、Taskが追加された際に、リストが更新されるよう、TasksAdapterクラスにセッターを追加します。TasksAdapterのtasksが更新されると、notifyDataSetChanged()が呼ばれ、リストが更新されるよう実装されております。

続いて、トーストを表示していたFABボタンタップイベントを修正します。追加するダミーデータのTaskを作成し、TaskDaoのinsert()を呼んで、Taskを追加しております。

なお、Taskのidは、EntityクラスでautoGenerate = trueが指定されております。この場合、idを0にしておく事で、自動的に一意のidが付与されます。

Taskを追加した後は、TaskDaoのgetAll()でTask一覧を取得し、それを、先ほど解説したTasksAdapterのtasksに指定する事で、リストの更新が行われます。

データの更新

続いて、データの更新処理を実装します。Taskの内容部分をタップすると、内容が”Updated Task”に更新されるよう実装します。

  • MainFragment.kt
class MainFragment : Fragment() {

    (省略)

    val itemListener = object : TaskItemListener {
        override fun onStateClick(task: Task) {
            Toast.makeText(activity, "onStateClick : " + task.description, Toast.LENGTH_SHORT).show()
        }

        override fun onDescriptionClick(task: Task) {
            task.description = "Updated Task"
            dao.update(task)
            adapter.tasks = dao.getAll()
        }

        override fun onDeleteClick(task: Task) {
            Toast.makeText(activity, "onDeleteClick : " + task.description, Toast.LENGTH_SHORT).show()
        }
    }

    (省略)
}

Taskの内容がタップされた際に呼ばれる、onDescriptionClick()を修正します。

onDescriptionClick()は、引数でTaskが渡ってくるので、そのTaskのdescriptionを”Updated Task”に変更します。そして、TaskDaoのupdate()を呼んで、Taskを更新します。

その後は、Task追加時と同じように、Taskの一覧を取得し直し、リストの更新を行います。

データの削除

最後に、データの削除処理を実装します。

  • MainFragment.kt
class MainFragment : Fragment() {

    (省略)

    val itemListener = object : TaskItemListener {
        override fun onStateClick(task: Task) {
            Toast.makeText(activity, "onStateClick : " + task.description, Toast.LENGTH_SHORT).show()
        }

        override fun onDescriptionClick(task: Task) {
            task.description = "Updated Task"
            dao.update(task)
            adapter.tasks = dao.getAll()
        }

        override fun onDeleteClick(task: Task) {
            dao.delete(task)
            adapter.tasks = dao.getAll()
        }
    }

    (省略)
}

Taskの削除ボタンがタップされた際に呼ばれる、onDeleteClick()を修正します。

onDeleteClick()は、引数でTaskが渡ってくるので、TaskDaoのdelete()の引数にそのTaskを指定し、データの削除を行います。

その後は、Task追加時と同じように、Taskの一覧を取得し直し、リストの更新を行います。

実行結果

この章の修正内容はDatabaseへのアクセス | GitHubからも確認頂けます。

今回の実装はここまでとなります。
次回は、Presenterを実装していく予定です。
ありがとうございました。