Compose页面加载时枚举状态莫名变更,求原因分析
我在Jetpack Compose中实现了一个视图,定义了包含SHOW_LOGO等值的AddReturnViewState枚举,希望根据用户操作切换显示不同组件。初始状态设为SHOW_LOGO,但页面加载后,logo仅显示瞬间就消失,推测枚举值被改为SHOW_DETAIL。请帮忙排查是什么操作更新了枚举值。
枚举定义
enum class AddReturnViewState { SHOW_LOGO, SHOW_BARCODE_WINDOW, SHOW_DETAIL, }
Composable代码
@Composable fun AddReturnItem( navController: NavController, viewModel: ReturnsViewModel, ) { var scannedCode by remember { mutableStateOf("") } val keyboardController = LocalSoftwareKeyboardController.current val focusRequester = remember { FocusRequester() } val focusManager = LocalFocusManager.current val customTextSelectionColors = TextSelectionColors( handleColor = OrangeTextFocusedColor, backgroundColor = Color.Transparent, ) val addReturnViewState = remember { mutableStateOf(SHOW_LOGO)} LaunchedEffect(Unit) { focusRequester.requestFocus() } Column( verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.CenterHorizontally, ) { Spacer(modifier = Modifier.height(16.dp)) Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) ) { IconButton( // Scan UPC with Camera Button onClick = { if (addReturnViewState.value == SHOW_LOGO || addReturnViewState.value == SHOW_DETAIL) { addReturnViewState.value = SHOW_BARCODE_WINDOW } else { addReturnViewState.value = SHOW_LOGO } } ) { Icon( painter = painterResource(id = R.drawable.ic_barcode2), contentDescription = "Scan with built in camera button", tint = if (isSystemInDarkTheme()) DarkModeIconTint else LightModeIconTint, modifier = Modifier.size(48.dp) ) } CompositionLocalProvider( LocalTextSelectionColors provides customTextSelectionColors, ) { OutlinedTextField( value = scannedCode, onValueChange = { scannedCode = it }, textStyle = LocalTextStyle.current.copy(fontSize = 17.sp), maxLines = 1, modifier = Modifier .fillMaxWidth() .focusRequester(focusRequester) .onFocusChanged { addReturnViewState.value = SHOW_LOGO } .padding(horizontal = 8.dp) .onKeyEvent { keyEvent ->// bluetooth scanner or hardware keyboard if (keyEvent.nativeKeyEvent.keyCode == KeyEvent.KEYCODE_ENTER) { processScannedCode( navController, viewModel, scannedCode, ) { addReturnViewState.value = it } scannedCode = "" focusManager.clearFocus() } true }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Text, imeAction = ImeAction.Done ), keyboardActions = KeyboardActions( // soft keyboard onDone = { keyboardController?.hide() processScannedCode( navController, viewModel, scannedCode, ) { addReturnViewState.value = it } scannedCode = "" focusManager.clearFocus() }), shape = RoundedCornerShape(10.dp), colors = TextFieldDefaults.textFieldColors( cursorColor = OrangeTextFocusedColor, focusedIndicatorColor = OrangeTextFocusedColor, ) ) } } Spacer(modifier = Modifier.height(16.dp)) if (addReturnViewState.value == SHOW_LOGO) { Spacer(modifier = Modifier.height(64.dp)) AsyncImage( model = ImageRequest.Builder(LocalContext.current) .data(SettingsHelper.getLogoUrl(LocalContext.current)) .crossfade(true) .build(), placeholder = painterResource(id = R.drawable.sls_logo), contentDescription = "Image of distribution center logo", contentScale = ContentScale.FillWidth, modifier = Modifier.fillMaxWidth(0.5f) ) } if (addReturnViewState.value == SHOW_BARCODE_WINDOW) { Log.d(ADD_RETURN_ITEM, "‼️ Showing BarcodeFragment") AndroidViewBinding(DecoratedBarcodeContainerBinding::inflate) { barcodeFragmentContainerView.getFragment<BarcodeFragment>() if (viewModel.barCodeScannerResult.value.isNotBlank()) { processScannedCode( navController, viewModel, viewModel.barCodeScannerResult.value, ) { addReturnViewState.value = it } } } } if (addReturnViewState.value == SHOW_DETAIL) { ReturnItemDetailView( navController, viewModel, enableTrashButton = true, enableActionButtonRow = false, ) { addReturnViewState.value = SHOW_LOGO } } } }
SettingsHelper相关代码
public class SettingsHelper { public static String getLogoUrl(Context context) { SharedPreferences sp = getSharedPreferencesRef(context); if (sp != null) { return sp.getString(LOGOURL, ""); } else { return ""; } } }
排查关键点
processScannedCode回调的返回值:
代码中多处调用processScannedCode并通过回调设置addReturnViewState.value = it,如果该函数在页面初始化时(比如viewModel存在缓存结果)返回SHOW_DETAIL,会直接切换状态。需要检查processScannedCode的内部逻辑,确认是否存在触发该回调的前置条件。viewModel.barCodeScannerResult初始值:
在SHOW_BARCODE_WINDOW分支中,直接判断viewModel.barCodeScannerResult.value.isNotBlank(),若viewModel的该状态初始不为空,会立即执行processScannedCode,进而通过回调修改状态为SHOW_DETAIL。添加日志定位状态修改点:
在所有修改addReturnViewState.value的位置添加日志,打印状态变化和调用栈,精准定位触发源:// 示例:修改状态时添加日志 addReturnViewState.value = SHOW_DETAIL Log.d("AddReturnState", "切换到SHOW_DETAIL,调用栈:${Exception().stackTraceToString()}")Composable重组的副作用:
确认viewModel中是否有其他状态变化导致Composable频繁重组,进而触发了未预期的状态修改逻辑。
内容的提问来源于stack exchange,提问作者jonathan3087

