Android Room存储值为2时单选按钮误判selected==0为true问题
问题描述
我在开发一个学校项目的Android应用,使用Room数据库存储用户选中的选项值。已经完成Room相关的Entity、Dao、Database类,以及ViewModel和Compose界面代码的编写。通过App Inspector确认数据库中存储的selected值为2,但启动SettingActivity时,单选按钮却默认选中第一个选项(判定selected==0为true)。
Entity代码
@Entity data class Selected( var selected: String, @PrimaryKey(autoGenerate = false) val id: Int = 0 )
Dao代码
@Dao interface SelectedDao { @Upsert suspend fun upsertSelected(selected: Selected) @Query("SELECT * FROM Selected") fun getSelected(): Flow<Selected> }
Database代码
@Database( entities = [Selected::class], version = 1 ) abstract class SelectedDatabase: RoomDatabase() { abstract val dao: SelectedDao }
ViewModel相关代码
sealed interface SelectedEvent { object SaveSelected: SelectedEvent } data class SelectedState ( val selected: Selected = Selected("0") ) class SelectedViewModel( private val dao: SelectedDao ): ViewModel() { private val _selected = dao.getSelected() .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), Selected("2")) private val _state = MutableStateFlow(SelectedState()) val state = combine(_state, _selected) { state, selected -> state.copy( selected = selected ) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), SelectedState()) fun onEvent(event: SelectedEvent) { when(event) { SelectedEvent.SaveSelected -> { val s = state.value.selected.selected if (s.isBlank()) return val selected = Selected(s) viewModelScope.launch { dao.upsertSelected(selected) } _state.update { it.copy( selected = Selected(s) ) } } } } }
Activity和Compose界面代码
class SettingActivity : ComponentActivity() { private val db by lazy { Room.databaseBuilder( applicationContext, SelectedDatabase::class.java, "selected.db" ).build() } private val viewModel by viewModels<SelectedViewModel>( factoryProducer = { object : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { return SelectedViewModel(db.dao) as T } } } ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val state by viewModel.state.collectAsState() RasporedTheme { Homescreen(state, viewModel::onEvent) } } } } @Composable private fun Homescreen( state: SelectedState, onEvent: (SelectedEvent) -> Unit ) { val selected: Int = state.selected.selected.toInt() Log.d("CHECK SELECTED VALUE", "${state.selected.selected} $selected") val radioButtons = remember { mutableStateListOf( ToggleableInfo( isChecked = selected == 0, text = "Prva" ), ToggleableInfo( isChecked = selected == 1, text = "Druga" ), ToggleableInfo( isChecked = selected == 2, text = "Treca" ) ) } Column( modifier = Modifier .fillMaxSize() .background(Color(0xff000000)), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(10.dp) ) { Text( text = "Settings", fontSize = 32.sp, color = Color.White, ) Column( modifier = Modifier .padding(10.dp, 0.dp, 10.dp, 0.dp) .clip(shape = RoundedCornerShape(25.dp)) .background(Color(0xff171717)) .padding(10.dp), ) { Text( text = "Grupa:", color = Color(0xfffafafa), fontSize = 20.sp, modifier = Modifier.padding(start = 5.dp) ) radioButtons.forEachIndexed { index, info -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .padding(end = 16.dp) .fillMaxWidth() .clickable { dataProcessor.grupa = index state.selected.selected = index.toString() onEvent(SelectedEvent.SaveSelected) radioButtons.replaceAll { it.copy( isChecked = it.text == info.text ) } } ) { RadioButton( selected = info.isChecked, onClick = { dataProcessor.grupa = index state.selected.selected = index.toString() radioButtons.replaceAll { it.copy( isChecked = it.text == info.text ) } } ) Text( text = info.text, color = Color(0xfffafafa), fontSize = 18.sp ) } } } } }
问题原因及解决方案
1. 单选按钮状态未跟随数据更新
- 问题:
radioButtons用remember初始化后,仅在Compose首次组合时计算选中状态。后续state从数据库加载到正确值2时,radioButtons不会自动更新,仍保持初始的selected==0判定结果。 - 修复:用
derivedStateOf让单选按钮状态自动跟随state.selected变化:
val radioButtons by remember { derivedStateOf { listOf( ToggleableInfo( isChecked = selected == 0, text = "Prva" ), ToggleableInfo( isChecked = selected == 1, text = "Druga" ), ToggleableInfo( isChecked = selected == 2, text = "Treca" ) ) } }
若需保留mutableStateListOf的可变性,可在selected变化时手动更新:
LaunchedEffect(selected) { radioButtons.apply { clear() addAll( listOf( ToggleableInfo(selected == 0, "Prva"), ToggleableInfo(selected == 1, "Druga"), ToggleableInfo(selected == 2, "Treca") ) ) } }
2. 直接修改不可变状态对象
- 问题:代码中直接修改
state.selected.selected = index.toString(),但SelectedState和Selected是不可变数据类,这种修改不会触发Compose重组,也不符合状态管理规范。 - 修复:扩展ViewModel事件体系,通过ViewModel更新状态:
// 扩展SelectedEvent sealed interface SelectedEvent { object SaveSelected: SelectedEvent data class UpdateSelected(val value: String): SelectedEvent } // ViewModel中处理UpdateSelected事件 fun onEvent(event: SelectedEvent) { when(event) { is SelectedEvent.UpdateSelected -> { _state.update { it.copy(selected = Selected(event.value)) } } SelectedEvent.SaveSelected -> { val s = state.value.selected.selected if (s.isBlank()) return val selected = Selected(s) viewModelScope.launch { dao.upsertSelected(selected) } } } }
在Compose中调用事件替代直接修改:
.clickable { dataProcessor.grupa = index onEvent(SelectedEvent.UpdateSelected(index.toString())) onEvent(SelectedEvent.SaveSelected) }
3. ViewState初始值与数据库初始值不一致
- 问题:
state的初始值是SelectedState()(默认选中0),数据库加载完成前界面会先显示这个初始值,导致首次渲染错误。 - 修复:将
state的初始值与_selected的初始值保持一致:
val state = combine(_state, _selected) { state, selected -> state.copy(selected = selected) }.stateIn( viewModelScope, SharingStarted.WhileSubscribed(5000), SelectedState(selected = Selected("2")) // 与_selected初始值匹配 )
内容的提问来源于stack exchange,提问作者MyGamesM
相关产品推荐
相关产品推荐

