[Android] 024. Room(DB)

今回はbuild.gradle.kts+Gradle Version Catalogsでの設定と019. ViewModel(lifecycle-viewmodel-compose:2.5.0)で紹介したパターンで実装してみました

Flowについては別途見ていくとしてとりあえずRoomを扱う場合の実装パターンの一つということではここでは説明省きます

developerの説明は以下
https://developer.android.com/training/data-storage/room?hl=ja

kspの設定

kspを使用できるようにします

kspのバージョンは以下で確認できました
https://github.com/google/ksp/releases

[versions]
com-google-devtools-ksp = "1.9.0-1.0.13"

[plugins]
com-google-devtools-ksp = {id = "com.google.devtools.ksp", version.ref = "com-google-devtools-ksp" }
plugins {
    alias(libs.plugins.com.google.devtools.ksp) apply false
}
plugins {
    alias(libs.plugins.com.google.devtools.ksp)
}

依存関係追加

以下をProject Structureなどでimplementationします

androidx.room:room-ktx
androidx.room:room-runtime
androidx.room:room-compiler

追加後以下のようにバージョンを指定しなおします

[versions]
androidx-room = "2.5.2"

[libraries]
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "androidx-room" }
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "androidx-room" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "androidx-room" }

room-compilerをkpsに変更します

dependencies {
    implementation(libs.androidx.room.ktx)
    implementation(libs.androidx.room.runtime)
    ksp(libs.androidx.room.compiler)
}

今回ViewModelを使用するのでandroidx.lifecycle:lifecycle-viewmodel-composeもimplementationします

実装

まずはサクッとデータ周りを実装

@Entity(tableName = "data_table")
data class Data(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    val name: String,
)
@Dao
interface DataDao {
    @Query("SELECT * FROM data_table WHERE id = :id")
    fun get(id: Int): Flow<Data>

    @Query("SELECT * FROM data_table ORDER BY id ASC")
    fun getAll(): Flow<List<Data>>

    @Query("SELECT * FROM data_table ORDER BY name ASC")
    fun getAllAlphabetized(): Flow<List<Data>>

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(data: Data)

    @Update(onConflict = OnConflictStrategy.REPLACE)
    suspend fun update(data: Data)

    @Delete
    suspend fun delete(data: Data)

    @Query("DELETE FROM data_table")
    suspend fun deleteAll()
}
@Database(entities = [Data::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
    abstract fun dao(): DataDao

    companion object {
        @Volatile
        private var Instance: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            return Instance ?: synchronized(this) {
                Room.databaseBuilder(context, AppDatabase::class.java, "app_database")
                    .build().also { Instance = it }
            }
        }
    }
}
interface DataRepository {
    fun getStream(id: Int): Flow<Data?>
    fun getAllStream(): Flow<List<Data>>
    fun getAllAlphabetizedStream(): Flow<List<Data>>
    suspend fun insert(item: Data)
    suspend fun update(item: Data)
    suspend fun delete(item: Data)
    suspend fun deleteAll()
}
class DefaultDataRepository(private val dao: DataDao) : DataRepository {
    override fun getStream(id: Int) = dao.get(id)
    override fun getAllStream() = dao.getAll()
    override fun getAllAlphabetizedStream() = dao.getAllAlphabetized()

    // ワーカースレッド以外で使用した場合警告
    @Suppress("RedundantSuspendModifier")
    @WorkerThread
    override suspend fun insert(data: Data) = dao.insert(data)

    @Suppress("RedundantSuspendModifier")
    @WorkerThread
    override suspend fun update(data: Data) = dao.update(data)

    @Suppress("RedundantSuspendModifier")
    @WorkerThread
    override suspend fun delete(data: Data) = dao.delete(data)

    @Suppress("RedundantSuspendModifier")
    @WorkerThread
    override suspend fun deleteAll() = dao.deleteAll()
}
interface AppContainer {
    val dataRepository: DataRepository
}

class AppDataContainer(private val context: Context) : AppContainer {
    override val dataRepository: DataRepository by lazy {
        DefaultDataRepository(AppDatabase.getDatabase(context).dao())
    }
}
/**
 * ```AndroidManifest.xml
 * <application
 *     android:name=".Application"
 * ```
 */
class Application : android.app.Application() {
    lateinit var container: AppContainer

    override fun onCreate() {
        super.onCreate()
        container = AppDataContainer(this)
    }
}

つぎにViewModelとViewを実装
Addボタンを押すとDBにデータが1つ追加され、CleanupボタンでDBのデータ全クリアです
DBが更新されるとUIにも反映されます

class DataListViewModel(private val repo: DataRepository) : ViewModel() {
    val dataList: StateFlow<List<Data>> = repo.getAllStream().stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = listOf()
    )

    fun insertData(data: Data) = viewModelScope.launch {
        repo.insert(data)
    }
    fun updateData(data: Data) = viewModelScope.launch {
        repo.update(data)
    }
    fun deleteData(data: Data) = viewModelScope.launch {
        repo.delete(data)
    }
    fun deleteAll() = viewModelScope.launch {
        repo.deleteAll()
    }
}
@Composable
fun DataListScreen(
    viewModel: DataListViewModel = androidx.lifecycle.viewmodel.compose.viewModel {
        val application = checkNotNull(get(ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY) as Application)
        DataListViewModel(application.container.dataRepository)
    },
    modifier: Modifier = Modifier
) {
    val dataList = viewModel.dataList.collectAsState()
    val names = listOf("alpha", "bravo", "charlie", "echo")

    Column(modifier) {
        Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
            Button(onClick = {
                val index = (0..3).random()
                viewModel.insertData(Data(name = names[index]))
            }) {
                Text("Add")
            }

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

        dataList.value?.let {
            LazyColumn {
                itemsIndexed(it) { index, data ->
                    ListItem(
                        headlineContent = {
                            Text("$index. ${data.name}(${data.id})")
                        },
                    )
                }
            }
        }
    }
}

Android Studio Giraffe 2022.3.1 Patch 1 built on August 17, 2023