公開日: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というアノテーションを使用することができます。
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を実装していく予定です。
ありがとうございました。