Jetpack Compose ViewModel监听mutableStateOf开关变更避免初始化触发更新的方案
问题根源
LaunchedEffect 会在指定的 key 首次进入组合树时默认执行一次回调,和值是否发生变化无关,这就是页面初始化就会触发网络请求的原因。
最优方案:单向数据流事件驱动(推荐)
完全抛弃副作用监听的实现方式,通过用户点击事件直接触发更新,从根源避免初始化调用,同时符合 Compose 单向数据流的设计规范,耦合度更低。
步骤1:改造 ProfileViewModel,封装状态变更逻辑
class ProfileViewModel : BaseViewModel() { // 对外暴露不可变状态,修改只能通过ViewModel提供的方法 var greet by mutableStateOf(user.pushSetting.greet) private set var message by mutableStateOf(user.pushSetting.message) private set var messageDetails by mutableStateOf(user.pushSetting.messageDetails) private set var like by mutableStateOf(user.pushSetting.like) private set var comment by mutableStateOf(user.pushSetting.comment) private set // 防抖任务,避免短时间多次切换触发多次请求 private var updateJob: Job? = null // 统一的状态修改入口,也可以为每个开关单独写方法 fun updatePushSetting(type: PushSettingType, newValue: Boolean) { when(type) { PushSettingType.GREET -> if (greet == newValue) return else greet = newValue PushSettingType.MESSAGE -> if (message == newValue) return else message = newValue PushSettingType.MESSAGE_DETAILS -> if (messageDetails == newValue) return else messageDetails = newValue PushSettingType.LIKE -> if (like == newValue) return else like = newValue PushSettingType.COMMENT -> if (comment == newValue) return else comment = newValue } updateUser() } private fun updateUser() { updateJob?.cancel() updateJob = viewModelScope.launch { delay(300) // 300ms防抖,短时间多次开关切换只会发起一次请求 // 此处写入你的网络请求逻辑,携带当前所有开关状态 } } } // 定义开关类型枚举 enum class PushSettingType { GREET, MESSAGE, MESSAGE_DETAILS, LIKE, COMMENT }
步骤2:改造 SwitchRow 组件,拆分状态和回调
不再直接传递 MutableState 到组件内,改为接收状态值和变更回调,降低组件耦合:
@Composable fun SwitchRow( text: String, line: Boolean = true, checked: Boolean, onCheckedChange: (Boolean) -> Unit ) { ListItem( text = { Text(text) }, trailing = { Switch( checked = checked, onCheckedChange = onCheckedChange, colors = SwitchDefaults.colors(checkedThumbColor = MaterialTheme.colors.primary) ) } ) }
步骤3:调整 SettingCard 调用逻辑
@Composable fun SettingCard(viewModel: ProfileViewModel) { Lists { Section { TextRow(text = "手机号码") { } TextRow(text = "修改密码", line = false) { } } Section { SwitchRow( text = "新好友通知", checked = viewModel.greet, onCheckedChange = { viewModel.updatePushSetting(PushSettingType.GREET, it) } ) SwitchRow( text = "新消息通知", checked = viewModel.message, onCheckedChange = { viewModel.updatePushSetting(PushSettingType.MESSAGE, it) } ) SwitchRow( text = "消息显示详细", line = false, checked = viewModel.messageDetails, onCheckedChange = { viewModel.updatePushSetting(PushSettingType.MESSAGE_DETAILS, it) } ) } } }
备选方案:不改动现有结构,加首次加载标记
如果你不想调整现有组件的传参结构,可以在 LaunchedEffect 中增加首次加载标记位,跳过初始化时的自动调用:
@Composable fun SettingCard(viewModel: ProfileViewModel) { var isFirstLoaded by remember { mutableStateOf(true) } // 把所有需要监听的开关值都作为key LaunchedEffect(viewModel.greet.value, viewModel.message.value, viewModel.messageDetails.value) { if (isFirstLoaded) { isFirstLoaded = false return@LaunchedEffect } viewModel.updateUser() } // 原有布局逻辑不变 Lists { // ... } }
内容的提问来源于stack exchange,提问作者gaohomway
相关产品推荐
相关产品推荐

