今回は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