You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 06:25:57