StateFlow which get data from Room database Dao, injected into viewmodel, is not updated ater dao.insertAll()

1 week ago 14
ARTICLE AD BOX

In my Android Jetpack Compose project with Room database the ViewModel is hilt-injected Dao and has property of type StateFlow, whose value is the result of calling dao.getAll(). I tried to prepopulate my database entity using a ViewModel function that calls dao.insertAll() and calling this function from a compose layout. The dao.insertAll() function successfully returns a list of entity IDs, but the corresponding ViewModel property of type StateFlow isn't updated. My ViewModel (partially)

@HiltViewModel class CalendarViewModel @Inject constructor(private val scheduleDao : ScheduleDao) : ViewModel() { val schedule : StateFlow<List<ScheduleEntity>> = scheduleDao.getAll().stateIn( scope = viewModelScope, started = SharingStarted.Lazily, initialValue = emptyList() ) private fun getScheduleEntities() : List<ScheduleEntity> { val scheduleEntities = mutableListOf<ScheduleEntity>() 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 <= endTime }.map { it.toKotlinLocalTime() }.toList() for(curDate in startDate .. endDate) { if(curDate.dayOfWeek != DayOfWeek.SATURDAY && curDate.dayOfWeek != DayOfWeek.SUNDAY) { val scheduleEntity = ScheduleEntity(curDate, timeList) scheduleEntities.add(scheduleEntity) } } } catch(e : Exception) { scheduleEntities.clear() Log.e("CalendarViewModel.fillDefaultSchedule", e.toString()) throw(e) } return scheduleEntities } fun fillScheduleEntities() { try { viewModelScope.launch(Dispatchers.IO) { var scheduleEntities = schedule.value if(scheduleEntities?.isEmpty() == true) { scheduleEntities = getScheduleEntities() val scheduleIds = scheduleDao.insertAll(scheduleEntities) val sizeString = schedule.value.size.toString() Log.i("CalendarViewModel.fillDefaultSchedule", "Schedule size after initial insertAll: $sizeString") } } } catch(e : Exception) { Log.e("CalendarViewModel.fillDefaultSchedule", e.toString()) throw(e) } } }

My entity:

@Entity data class ScheduleEntity( val date : LocalDate, val availableTime : List<LocalTime>, @PrimaryKey(autoGenerate = true) val id : Int = 0 )

Type converters:

class Converters { val timeFormat = LocalTime.Format { hour(); char(':'); minute(); char(':'); second() } @TypeConverter fun localDateToTimestamp(date : LocalDate?) : Long? { return date?.atStartOfDayIn(TimeZone.currentSystemDefault())?.toEpochMilliseconds() } @TypeConverter fun timestampToLocalDate(timestamp : Long?) : LocalDate? { return if(timestamp != null) Instant.fromEpochMilliseconds(timestamp).toLocalDateTime(TimeZone.currentSystemDefault()).date else null } @TypeConverter fun listLocalTimeToString(list : List<LocalTime>?) : String? { return list?.joinToString { item -> item.format(timeFormat) } } @TypeConverter fun stringToListLocalTime(value : String?) : List<LocalTime>? { return value?.split(',')?.map { item -> LocalTime.parse(item.trim(), timeFormat) } } }

Dao:

@Dao interface ScheduleDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertOne(vararg scheduleEntity : ScheduleEntity) @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAll(scheduleEntities : List<ScheduleEntity>): List<Long> @Delete suspend fun delete(scheduleEntity : ScheduleEntity) @Query("SELECT * FROM ScheduleEntity") fun getAll() : Flow<List<ScheduleEntity>> }

Room database and Hilt module

@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() }

My compose layout (partially)

@Composable fun MainLayout(modifier: Modifier? = null, viewModel : CalendarViewModel = hiltViewModel()) { val schedule by viewModel.schedule.collectAsStateWithLifecycle() viewModel.fillScheduleEntities() }

I also tried using refreshTrigger:

private val refreshTrigger = MutableSharedFlow<Unit>(replay = 1).apply { tryEmit(Unit) } val schedule : StateFlow<List<ScheduleEntity>> = refreshTrigger.flatMapLatest { scheduleDao.getAll() }.stateIn( scope = viewModelScope, started = SharingStarted.Lazily, initialValue = emptyList() ) ... scheduleEntities = getScheduleEntities() val scheduleIds = scheduleDao.insertAll(scheduleEntities) refreshTrigger.emit(Unit)

But it didn't help. How can I solve this problem?

Read Entire Article