Anonymous
StateFlow, который получает данные из базы данных Room Dao, внедренные в модель представления, не обновляется после dao.
Сообщение
Anonymous » 19 янв 2026, 19:34
В моем проекте Android Jetpack Compose с базой данных Room ViewModel представляет собой Dao с внедрением рукоятки и имеет свойство типа StateFlow, значение которого является результатом вызова dao.getAll(). Я попытался предварительно заполнить объект моей базы данных, используя функцию ViewModel, которая вызывает dao.insertAll() и вызывает эту функцию из макета создания. Функция dao.insertAll() успешно возвращает список идентификаторов объектов, но соответствующее свойство ViewModel типа StateFlow не обновляется.
Моя ViewModel (частично)
Код: Выделить всё
@HiltViewModel
class CalendarViewModel @Inject constructor(private val scheduleDao : ScheduleDao) : ViewModel() {
private val selectedDate = MutableStateFlow
(LocalDate(2026, 1, 1))
val selectedDateUiState = selectedDate.asStateFlow()
val schedule : StateFlow = scheduleDao.getAll().stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = emptyList()
)
fun onDateChanged(dateMillis : Long?) {
if(dateMillis != null) {
selectedDate.value = Instant.fromEpochMilliseconds(dateMillis)
.toLocalDateTime(TimeZone.currentSystemDefault()).date
}
}
private fun getScheduleEntities() : List {
val scheduleEntities = mutableListOf()
try {
val startDate = LocalDate(2000, 1, 1)
val endDate = java.time.LocalDate.now().toKotlinLocalDate()
val startTime = java.time.LocalTime.of(9, 0, 0)
val endTime = java.time.LocalTime.of(18, 0, 0)
val timeList = generateSequence(startTime) { it.plusMinutes(30) }.takeWhile { it item.format(timeFormat) }
}
@TypeConverter
fun stringToListLocalTime(value : String?) : List? {
return value?.split(',')?.map { item -> LocalTime.parse(item.trim(), timeFormat) }
}
}
Дао:
Код: Выделить всё
@Dao
interface ScheduleDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOne(vararg scheduleEntity : ScheduleEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(scheduleEntities : List): List
@Delete
suspend fun delete(scheduleEntity : ScheduleEntity)
@Query("SELECT * FROM ScheduleEntity")
fun getAll() : Flow
}
База данных комнат и модуль Hilt
Код: Выделить всё
@Database(entities = [ScheduleEntity::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class CalendarDatabase : RoomDatabase() {
abstract fun scheduleDao() : ScheduleDao
companion object {
var INSTANCE : CalendarDatabase? = null
fun getDatabase(context: Context) : CalendarDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(context.applicationContext, CalendarDatabase::class.java, "tasks")
.enableMultiInstanceInvalidation()
.build()
INSTANCE = instance
return instance
}
}
}
}
@Module
@InstallIn(SingletonComponent::class)
object TasksModule {
@Provides
@Singleton
fun provideApplicationScope(): CoroutineScope {
return CoroutineScope(SupervisorJob() + Dispatchers.IO)
}
@Provides
@Singleton
fun provideDatabase(
@ApplicationContext context: Context) : CalendarDatabase = CalendarDatabase.getDatabase(context)
@Provides
@Singleton
fun provideScheduleDao(calendarDatabase : CalendarDatabase) : ScheduleDao = calendarDatabase.scheduleDao()
}
Мой макет создания:
Код: Выделить всё
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainLayout(modifier: Modifier? = null, viewModel : CalendarViewModel = hiltViewModel()) {
val selectedDate by viewModel.selectedDateUiState.collectAsStateWithLifecycle()
val schedule by viewModel.schedule.collectAsStateWithLifecycle()
val isLoaded by viewModel.isLoadedUiState.collectAsStateWithLifecycle()
val datePickerState = rememberDatePickerState(initialSelectedDate = selectedDate.toJavaLocalDate())
LaunchedEffect(datePickerState.selectedDateMillis) {
viewModel.onDateChanged(datePickerState.selectedDateMillis)
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
modifier = (modifier ?: Modifier)
.fillMaxWidth()
.fillMaxHeight()
) {
if(isLoaded) {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
.wrapContentHeight()
) {
DatePicker(
state = datePickerState,
modifier = Modifier.size(400.dp)
)
}
SelectedDateAvailableTimeList(Modifier.weight(1f), viewModel)
}
else {
CircularProgressIndicator(
modifier = Modifier.size(200.dp),
color = Color.Blue
)
}
}
}
@Composable
fun SelectedDateAvailableTimeList(modifier : Modifier, viewModel : CalendarViewModel = hiltViewModel()) {
val time by viewModel.selectedDateAvailableTime.collectAsState()
LazyVerticalGrid(
columns = GridCells.Fixed(7),
userScrollEnabled = false,
horizontalArrangement = Arrangement.spacedBy(space = 10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
modifier = modifier.fillMaxWidth().wrapContentHeight().padding(0.dp, 10.dp)
) {
items(time) { curTime ->
BasicTextField(
modifier = Modifier
.wrapContentSize(Alignment.Center),
textStyle = TextStyle(
textAlign = TextAlign.Center,
fontSize = 10.sp,
fontWeight = FontWeight.Normal,
color = Black),
readOnly = true,
value = curTime.format(viewModel.timeFormat),
onValueChange = {},
)
}
}
}
Мои библиотеки libs.versions.toml:
Код: Выделить всё
[versions]
agp = "8.13.2"
kotlin = "2.3.0"
coreKtx = "1.17.0"
lifecycleRuntimeKtx = "2.10.0"
activityCompose = "1.12.2"
composeBom = "2025.12.01"
ksp = "2.3.0"
hilt = "2.57.2"
hiltAndroid = "2.57.2"
hiltAndroidCompiler = "2.57.2"
roomRuntime = "2.8.4"
roomCompiler = "2.8.4"
roomKtx = "2.8.4"
kotlinxDatetime = "0.7.1"
lifecycleViewmodelCompose = "2.10.0"
hiltNavigationCompose = "1.3.0"
lifecycleRuntimeCompose = "2.10.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hiltAndroid" }
symbol-processing-api = { group = "com.google.devtools.ksp", name = "symbol-processing-api", version.ref = "ksp" }
hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hiltAndroidCompiler" }
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "roomRuntime" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "roomCompiler" }
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "roomKtx" }
kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeCompose" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
Мой build.gradle.kts:
Код: Выделить всё
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
}
android {
namespace = "app.calendar"
compileSdk {
version = release(36)
}
defaultConfig {
applicationId = "app.calendar"
minSdk = 33
targetSdk = 36
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
getByName("debug") {
isDebuggable = true
signingConfig = signingConfigs.getByName("debug")
}
}
testBuildType = "debug"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlin {
jvmToolchain(17)
}
buildFeatures {
compose = true
}
tasks.withType().configureEach {
enabled = false
}
androidComponents {
beforeVariants { variantBuilder ->
(variantBuilder as HasHostTestsBuilder).hostTests.get(HostTestBuilder.UNIT_TEST_TYPE)?.enable = false
variantBuilder.enableAndroidTest = false
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
implementation(libs.symbol.processing.api)
implementation(libs.hilt.android)
implementation(libs.androidx.lifecycle.runtime.compose)
ksp(libs.hilt.android.compiler)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.room.runtime)
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.room.ktx)
implementation(libs.kotlinx.datetime)
implementation(libs.androidx.lifecycle.viewmodel.compose)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
}
Я также пробовал использовать RefreshTrigger:
Код: Выделить всё
private val refreshTrigger = MutableSharedFlow(replay = 1).apply { tryEmit(Unit) }
val schedule : StateFlow = refreshTrigger.flatMapLatest { scheduleDao.getAll() }.stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = emptyList()
)
...
scheduleEntities = getScheduleEntities()
val scheduleIds = scheduleDao.insertAll(scheduleEntities)
refreshTrigger.emit(Unit)
Но это не помогло. Как я могу решить эту проблему?
Репозиторий моего проекта
Подробнее здесь:
https://stackoverflow.com/questions/798 ... del-is-not
1768840464
Anonymous
В моем проекте Android Jetpack Compose с базой данных Room ViewModel представляет собой Dao с внедрением рукоятки и имеет свойство типа StateFlow, значение которого является результатом вызова dao.getAll(). Я попытался предварительно заполнить объект моей базы данных, используя функцию ViewModel, которая вызывает dao.insertAll() и вызывает эту функцию из макета создания. Функция dao.insertAll() успешно возвращает список идентификаторов объектов, но соответствующее свойство ViewModel типа StateFlow не обновляется. Моя ViewModel (частично) [code]@HiltViewModel class CalendarViewModel @Inject constructor(private val scheduleDao : ScheduleDao) : ViewModel() { private val selectedDate = MutableStateFlow (LocalDate(2026, 1, 1)) val selectedDateUiState = selectedDate.asStateFlow() val schedule : StateFlow = scheduleDao.getAll().stateIn( scope = viewModelScope, started = SharingStarted.Lazily, initialValue = emptyList() ) fun onDateChanged(dateMillis : Long?) { if(dateMillis != null) { selectedDate.value = Instant.fromEpochMilliseconds(dateMillis) .toLocalDateTime(TimeZone.currentSystemDefault()).date } } private fun getScheduleEntities() : List { val scheduleEntities = mutableListOf() try { val startDate = LocalDate(2000, 1, 1) val endDate = java.time.LocalDate.now().toKotlinLocalDate() val startTime = java.time.LocalTime.of(9, 0, 0) val endTime = java.time.LocalTime.of(18, 0, 0) val timeList = generateSequence(startTime) { it.plusMinutes(30) }.takeWhile { it item.format(timeFormat) } } @TypeConverter fun stringToListLocalTime(value : String?) : List? { return value?.split(',')?.map { item -> LocalTime.parse(item.trim(), timeFormat) } } } [/code] Дао: [code]@Dao interface ScheduleDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertOne(vararg scheduleEntity : ScheduleEntity) @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAll(scheduleEntities : List): List @Delete suspend fun delete(scheduleEntity : ScheduleEntity) @Query("SELECT * FROM ScheduleEntity") fun getAll() : Flow } [/code] База данных комнат и модуль Hilt [code]@Database(entities = [ScheduleEntity::class], version = 1, exportSchema = false) @TypeConverters(Converters::class) abstract class CalendarDatabase : RoomDatabase() { abstract fun scheduleDao() : ScheduleDao companion object { var INSTANCE : CalendarDatabase? = null fun getDatabase(context: Context) : CalendarDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder(context.applicationContext, CalendarDatabase::class.java, "tasks") .enableMultiInstanceInvalidation() .build() INSTANCE = instance return instance } } } } @Module @InstallIn(SingletonComponent::class) object TasksModule { @Provides @Singleton fun provideApplicationScope(): CoroutineScope { return CoroutineScope(SupervisorJob() + Dispatchers.IO) } @Provides @Singleton fun provideDatabase( @ApplicationContext context: Context) : CalendarDatabase = CalendarDatabase.getDatabase(context) @Provides @Singleton fun provideScheduleDao(calendarDatabase : CalendarDatabase) : ScheduleDao = calendarDatabase.scheduleDao() } [/code] Мой макет создания: [code]@OptIn(ExperimentalMaterial3Api::class) @Composable fun MainLayout(modifier: Modifier? = null, viewModel : CalendarViewModel = hiltViewModel()) { val selectedDate by viewModel.selectedDateUiState.collectAsStateWithLifecycle() val schedule by viewModel.schedule.collectAsStateWithLifecycle() val isLoaded by viewModel.isLoadedUiState.collectAsStateWithLifecycle() val datePickerState = rememberDatePickerState(initialSelectedDate = selectedDate.toJavaLocalDate()) LaunchedEffect(datePickerState.selectedDateMillis) { viewModel.onDateChanged(datePickerState.selectedDateMillis) } Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Top, modifier = (modifier ?: Modifier) .fillMaxWidth() .fillMaxHeight() ) { if(isLoaded) { Row( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() .wrapContentHeight() ) { DatePicker( state = datePickerState, modifier = Modifier.size(400.dp) ) } SelectedDateAvailableTimeList(Modifier.weight(1f), viewModel) } else { CircularProgressIndicator( modifier = Modifier.size(200.dp), color = Color.Blue ) } } } @Composable fun SelectedDateAvailableTimeList(modifier : Modifier, viewModel : CalendarViewModel = hiltViewModel()) { val time by viewModel.selectedDateAvailableTime.collectAsState() LazyVerticalGrid( columns = GridCells.Fixed(7), userScrollEnabled = false, horizontalArrangement = Arrangement.spacedBy(space = 10.dp), verticalArrangement = Arrangement.spacedBy(10.dp), modifier = modifier.fillMaxWidth().wrapContentHeight().padding(0.dp, 10.dp) ) { items(time) { curTime -> BasicTextField( modifier = Modifier .wrapContentSize(Alignment.Center), textStyle = TextStyle( textAlign = TextAlign.Center, fontSize = 10.sp, fontWeight = FontWeight.Normal, color = Black), readOnly = true, value = curTime.format(viewModel.timeFormat), onValueChange = {}, ) } } } [/code] Мои библиотеки libs.versions.toml: [code][versions] agp = "8.13.2" kotlin = "2.3.0" coreKtx = "1.17.0" lifecycleRuntimeKtx = "2.10.0" activityCompose = "1.12.2" composeBom = "2025.12.01" ksp = "2.3.0" hilt = "2.57.2" hiltAndroid = "2.57.2" hiltAndroidCompiler = "2.57.2" roomRuntime = "2.8.4" roomCompiler = "2.8.4" roomKtx = "2.8.4" kotlinxDatetime = "0.7.1" lifecycleViewmodelCompose = "2.10.0" hiltNavigationCompose = "1.3.0" lifecycleRuntimeCompose = "2.10.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" } androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hiltAndroid" } symbol-processing-api = { group = "com.google.devtools.ksp", name = "symbol-processing-api", version.ref = "ksp" } hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hiltAndroidCompiler" } androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "roomRuntime" } androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "roomCompiler" } androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "roomKtx" } kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" } androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" } androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" } androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeCompose" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } [/code] Мой build.gradle.kts: [code]plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) alias(libs.plugins.ksp) alias(libs.plugins.hilt) } android { namespace = "app.calendar" compileSdk { version = release(36) } defaultConfig { applicationId = "app.calendar" minSdk = 33 targetSdk = 36 versionCode = 1 versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } getByName("debug") { isDebuggable = true signingConfig = signingConfigs.getByName("debug") } } testBuildType = "debug" compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { jvmToolchain(17) } buildFeatures { compose = true } tasks.withType().configureEach { enabled = false } androidComponents { beforeVariants { variantBuilder -> (variantBuilder as HasHostTestsBuilder).hostTests.get(HostTestBuilder.UNIT_TEST_TYPE)?.enable = false variantBuilder.enableAndroidTest = false } } } dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.material3) implementation(libs.symbol.processing.api) implementation(libs.hilt.android) implementation(libs.androidx.lifecycle.runtime.compose) ksp(libs.hilt.android.compiler) implementation(libs.androidx.hilt.navigation.compose) implementation(libs.androidx.room.runtime) ksp(libs.androidx.room.compiler) implementation(libs.androidx.room.ktx) implementation(libs.kotlinx.datetime) implementation(libs.androidx.lifecycle.viewmodel.compose) debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.test.manifest) } [/code] Я также пробовал использовать RefreshTrigger: [code]private val refreshTrigger = MutableSharedFlow(replay = 1).apply { tryEmit(Unit) } val schedule : StateFlow = refreshTrigger.flatMapLatest { scheduleDao.getAll() }.stateIn( scope = viewModelScope, started = SharingStarted.Lazily, initialValue = emptyList() ) ... scheduleEntities = getScheduleEntities() val scheduleIds = scheduleDao.insertAll(scheduleEntities) refreshTrigger.emit(Unit) [/code] Но это не помогло. Как я могу решить эту проблему? Репозиторий моего проекта Подробнее здесь: [url]https://stackoverflow.com/questions/79868934/stateflow-which-get-data-from-room-database-dao-injected-into-viewmodel-is-not[/url]