Roomでデータベースに値を保存

# はじめに
[前回](https://qiita.com/karass/items/4f2e33446eb0253859ef)は、入力した値をリスト表示するところまで行いましいた。今回は、そのデータをデータベースに保存するようにします。

# 目標
Roomを追加するだけなので、今回はレイアウトの変更はありません。
アプリを閉じて、再起動したときにデータが保持されてるかを確認してください。

# 環境
- Android Studio 3.6.3
- Kotlin 1.3.72

# 作成手順
## Gradle

```build.gradle
apply plugin: 'kotlin-kapt'
...
dependencies {
    ...
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"

    def room_version = "2.2.5"
    implementation "androidx.room:room-runtime:$room_version"
    implementation "androidx.room:room-ktx:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
}
```

## 実装
Roomは以下の3つの要素で構成されています。
 - Entity
 - Dao (DataAccessObjects)
 - Room Database

### Entity
データベース内のテーブルを示します。
各Entityは、少なくとも一つの主キーを定義する必要があります。それは`@PrimaryKey`アノテーションで定義できます。
`autoGenerate`を設定すると自動でPrimaryKeyを発行してくれます。

```Todo.kt
@Entity
data class Todo(
    @PrimaryKey(autoGenerate = true) val tid: Int,
    @ColumnInfo(name = "todo_title") val todoTitle: String
)
```

### Dao
Daoではデータベースにアクセスするためのメソッドを定義します。
Interfaceに`@Dao`をつけるだけで定義できます。
今回は、挿入と全データの取得を行うメソッドを定義しました。
`getAll()`の戻り値を`LiveData`にすることにより、`Room`がデータベースに変更があった場合に通知してくれる内部コードを生成してくれるらしいです。とても便利ですね。

```TodoDao.kt
@Dao
interface TodoDao {

    @Insert
    suspend fun insert(todo: Todo)

    @Query("SELECT * FROM Todo")
    fun getAll(): LiveData<List<Todo>>
}
```

### Database
データベースの定義では、抽象クラスに`@Database`をつけると定義できます。
先程定義したEntityを`entities`に指定します。
そしてDaoを取得するためのメソッドを用意します。

```TodoDatabase.kt
@Database(entities = [Todo::class], version = 1)
abstract class TodoDatabase : RoomDatabase() {
    abstract fun todoDao(): TodoDao
}
```

### Repository
ViewModelとデータベースの中継としてRepositoryを作成します。

```TodoRepository.kt
class TodoRepository(private val todoDao: TodoDao) {
    val allTodoList = todoDao.getAll()

    @WorkerThread
    suspend fun insert(todo: Todo) {
        todoDao.insert(todo)
    }
}
```

### ViewModel
Repositoryからデータを受け取って処理します。
データベースの処理はメインスレッドで実行することができないので、Coroutineを使って非同期処理を行います。

```MainViewModel.kt
class MainViewModel(private val repository: TodoRepository) : ViewModel() {
    val todoList = repository.allTodoList

    fun insert(todo: Todo) = viewModelScope.launch {
        repository.insert(todo)
    }
}
```

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

    private lateinit var mainViewModel: MainViewModel
    private lateinit var adapter: RecyclerAdapter

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

        val db = Room.databaseBuilder(this, TodoDatabase::class.java, "database_name").build()
        val dao = db.todoDao()
        val repository = TodoRepository(dao)
        mainViewModel = MainViewModel(repository)

        adapter = RecyclerAdapter()
        main_recycler_view.layoutManager = LinearLayoutManager(this)
        main_recycler_view.adapter = adapter
        main_recycler_view.setHasFixedSize(true)

        add_item_button.setOnClickListener {
            mainViewModel.insert(Todo(0, submit_text.text.toString()))
        }

        mainViewModel.todoList.observe(this, Observer {
            adapter.setItem(it)
        })

    }
}
```

### Adapter
新しく加えたデータをAdapterにセットします。

```RecyclerAdapter.kt
class RecyclerAdapter : RecyclerView.Adapter<RecyclerAdapter.RecyclerViewHolder>(){

    private val todoList = mutableListOf<Todo>()

    fun setItem(items: List<Todo>) {
        todoList.clear()
        todoList.addAll(items)
        notifyDataSetChanged()
    }
    ...
}
```

# おわりに
`Room`を使用してデータベースに値を保存することができました。
次回は削除機能の追加をしたいと思います。
最近勉強したDaggerを使って、DIしてみてもいいかな！