[Android] 018. Room(DB)

Roomを使用してローカルデータベースを扱う

以下が参考になるかもです
https://developer.android.com/training/data-storage/room?hl=ja

設定

Project Structure

androidx.room:room-ktxなど最新バージョンを確認してVariablesでroom_versionを追加する
room_version 2.4.3

以下のライブラリをroom_versionで追加
androidx.room:room-ktx
androidx.room:room-runtime
androidx.room:room-compiler

ksp

使用しているKotlinのバージョンにあった設定をする

buildscript {
    ext {
        room_version = '2.4.3'
    }
}
plugins {
    id 'com.google.devtools.ksp' version '1.7.20-1.0.7' apply false
}

pluginsにid ‘com.google.devtools.ksp’ を追加
androidx.room:room-compilerのimplementationをkspに変更

plugins {
    id 'com.google.devtools.ksp'
}
dependencies {
    implementation "androidx.room:room-ktx:$room_version"
    implementation "androidx.room:room-runtime:$room_version"
    ksp "androidx.room:room-compiler:$room_version"
}

実装

Flow / StateFlow を使用したRoomの実装例です

clickボタンを押すとname = “test_(押した回数)”とnum = ランダム(0-99)がDBに追加されてリストに表示されます

まずDBで使用するテーブルをdata classで、次にDAO(Data Access Object)を作成
RoomDatabaseの定義、DB操作のRepository、ViewModelを作成してます

RoomDatabaseにApplicationContextを渡す必要があるためAndroidViewModelにしています

@Composable fun AppContent(viewModel: MainViewModel = viewModel()) {
    val datasList = viewModel.datasList.collectAsState()
    var count: Int by rememberSaveable { mutableStateOf( 0) }

    MaterialTheme {
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colorScheme.background
        ) {
            Column {
                Row(modifier = Modifier.padding(4.dp)) {
                    Button(onClick = {
                        val name = "test_${++count}"
                        if (datasList.value.find { it.name == name } == null) {
                            viewModel.insert(Datas(name, SecureRandom().nextInt(100)))
                        }
                        else {
                            viewModel.update(Datas(name, SecureRandom().nextInt(100)))
                        }
                    }) {
                        Text("click")
                    }

                    Button(onClick = { viewModel.deleteAll() }) {
                        Text("clear")
                    }
                }

                datasList.value?.let { datas ->
                    LazyColumn {
                        items(datas) { item ->
                            Text("${item.name} ${item.num}")
                        }
                    }
                }
            }
        }
    }
}

@Entity(tableName = "datas_table")
data class Datas(
    @PrimaryKey @ColumnInfo(name = "name") var name: String = "",
    @ColumnInfo(name = "num") var num: Int = 0,
)

@Dao interface DatasDao {
    @Query("SELECT * FROM datas_table ORDER BY name ASC")
    fun getAlphabetizedDatasList(): Flow<List<Datas>>

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(datas: Datas)

    @Update(onConflict = OnConflictStrategy.REPLACE)
    suspend fun update(datas: Datas)

    @Query("DELETE FROM datas_table")
    suspend fun deleteAll()
}

@Database(entities = [Datas::class], version = 1, exportSchema = false)
abstract class DatasRoomDatabase : RoomDatabase() {
    abstract fun dao(): DatasDao

    companion object {
        @Volatile private var INSTANCE: DatasRoomDatabase? = null

        fun getDatabase(context: Context): DatasRoomDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    DatasRoomDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                // return instance
                instance
            }
        }
    }
}

class DatasRepository(
    application: Application,
    private val db: DatasRoomDatabase = DatasRoomDatabase.getDatabase(application),
    private val dao: DatasDao = db.dao()
) {
    val datasList = dao.getAlphabetizedDatasList()

    // ワーカースレッド以外で使用した場合警告
    @Suppress("RedundantSuspendModifier")
    @WorkerThread
    suspend fun insert(datas: Datas) {
        dao.insert(datas)
    }
    @Suppress("RedundantSuspendModifier")
    @WorkerThread
    suspend fun update(datas: Datas) {
        dao.update(datas)
    }
    @Suppress("RedundantSuspendModifier")
    @WorkerThread
    suspend fun deleteAll() {
        dao.deleteAll()
    }
}

class MainViewModel(
    application: Application
) : AndroidViewModel(application) {
    private val repo: DatasRepository = DatasRepository(getApplication())
    val datasList: StateFlow<List<Datas>> = repo.datasList.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = listOf()
    )

    fun insert(datas: Datas) = viewModelScope.launch {
        repo.insert(datas)
    }
    fun update(datas: Datas) = viewModelScope.launch {
        repo.update(datas)
    }
    fun deleteAll() = viewModelScope.launch {
        repo.deleteAll()
    }
}

Android Studio Dolphin 2021.3.1 Patch 1 built on September 30, 2022