AndroidStudio: Room の使い方 （その1）

こちらと同様のことを行いました。
[[Kotlin]Roomを用いたデータベース操作](https://qiita.com/ijichi_y/items/c9b967810b3192a9a7ac)

## プロジェクトの作成
プロジェクト名: Room01

## 環境
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179446/79e0d2d3-caae-6a11-1c16-9321e1c188c7.png)

```build.gradle.kts_Project_Room01
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id("com.android.application") version "8.1.2" apply false
    id("org.jetbrains.kotlin.android") version "1.9.0" apply false
    id("com.google.devtools.ksp") version "1.9.20-1.0.14" apply false
}
```

```build.gradle.kts_Module_app
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("com.google.devtools.ksp")
}
（省略）
android {
（省略）
    buildFeatures {
        viewBinding = true
    }
}

dependencies {
    val room_version = "2.6.0"

    //Room

    implementation("androidx.room:room-runtime:$room_version")
    ksp("androidx.room:room-compiler:$room_version")
    implementation("androidx.room:room-rxjava2:$room_version")
    implementation("androidx.room:room-rxjava3:$room_version")
    implementation("androidx.room:room-guava:$room_version")
    testImplementation("androidx.room:room-testing:$room_version")
    implementation("androidx.room:room-paging:$room_version")

    // kotlin coroutines 非同期、メインスレッド処理用
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3")

    implementation("androidx.core:core-ktx:1.9.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.10.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
```

## 画面

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="64dp"
        android:paddingHorizontal="16dp"
        android:text="Hello World!" />

    <Button
        android:id="@+id/addButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="32dp"
        android:text="データ追加" />

    <Button
        android:id="@+id/displayButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="32dp"
        android:text="データ表示" />

</LinearLayout>
```

## プログラム
ツリー構造
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179446/0356e026-1d7d-d12d-5fe2-2b85efd9abab.png)

```MainActivity.kt
package com.example.room01

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.room.Room
import com.example.room01.databinding.ActivityMainBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
binding.textView.text = "こんにちは"
        val database = Room.databaseBuilder(
                this, RoomSampleDatabase::class.java, "room_sample_database.db")
                .fallbackToDestructiveMigration()
                .build()
        val notificationDao = database.notificationDao()

        binding.addButton.setOnClickListener {
            GlobalScope.launch(Dispatchers.IO) { // 非同期処理
                val lastId = notificationDao.selectLastId()
                val state = NotificationState(lastId + 1, "いいねをもらいました\n")
                notificationDao.insert(state)

                GlobalScope.launch(Dispatchers.Main) {  // main thread
                    Toast.makeText(this@MainActivity, "データ追加しました", Toast.LENGTH_SHORT).show()
                }
            }
        }

        binding.displayButton.setOnClickListener {
            GlobalScope.launch(Dispatchers.IO) { // 非同期処理
                val states = notificationDao.selectAll()
                GlobalScope.launch(Dispatchers.Main) {  // main thread
                    binding.textView.text = states.toString()
                }
            }

        }
    }
}
```

```NotificationDao.kt
package com.example.room01

import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query

@Dao
interface NotificationDao {
    // アイテム追加
    // onConflict = OnConflictStrategy.REPLACE しておくとデータが重複した場合でも自動でupdateにしてくれる insertといいつつupsertです
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(state: NotificationState)

    // 複数のアイテム追加
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(states: List<NotificationState>)

    // idを指定して取得
    @Query("SELECT * FROM notification WHERE id = :id LIMIT 1")
    fun select(id: String): NotificationState

    // 最後のid取得
    @Query("SELECT id FROM notification ORDER BY id DESC LIMIT 1")
    fun selectLastId(): Int

    // 全てのアイテム取得
    @Query("SELECT * FROM notification")
    fun selectAll(): List<NotificationState>

    // 全てのアイテム取得 LiveData
    @Query("SELECT * FROM notification")
    fun selectAllWithLiveData(): LiveData<List<NotificationState>>

    // データを全て削除
    @Query("DELETE FROM notification")
    fun deleteAll()
}
```

```NotificationState.kt
package com.example.room01

import androidx.room.ColumnInfo
import androidx.room.Entity

@Entity(
    tableName = "notification",
    primaryKeys = ["id"]
)
data class NotificationState(
    @ColumnInfo(name = "id") val id: Int = 0,
    @ColumnInfo(name = "message") val message: String
)
```

```RoomSampleDatabase.kt
package com.example.room01

import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters

@Database(
    entities = [NotificationState::class],
    version = 1
)

@TypeConverters
abstract class RoomSampleDatabase : RoomDatabase() {

    abstract fun notificationDao(): NotificationDao

}
```

## 実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179446/022f1b55-8e8f-4e87-9e10-40d606049876.png)

## Terminal でデータの確認

```bash
adb -s emulator-5554 shell
```

```text
$ adb -s emulator-5554 shell
emu64xa:/ $ run-as com.example.room01
emu64xa:/data/user/0/com.example.room01 $ ls      
cache  code_cache  databases  files
emu64xa:/data/user/0/com.example.room01 $ ls databases
room_sample_database.db      room_sample_database.db-wal
room_sample_database.db-shm
qlite3 databases/room_sample_database.db                                      <
SQLite version 3.39.2 2022-07-21 15:24:47
Enter ".help" for usage hints.
sqlite> .table
android_metadata   notification       room_master_table
sqlite> select * from notification;
1|いいねをもらいました

2|いいねをもらいました

3|いいねをもらいました

4|いいねをもらいました

5|いいねをもらいました

sqlite>.quit
```