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

Compose页面加载时枚举状态莫名变更,求原因分析

问题排查:Jetpack Compose中Logo显示后瞬间消失的状态切换问题

我在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 "";
        }
    }
}

排查关键点

  1. processScannedCode回调的返回值:
    代码中多处调用processScannedCode并通过回调设置addReturnViewState.value = it,如果该函数在页面初始化时(比如viewModel存在缓存结果)返回SHOW_DETAIL,会直接切换状态。需要检查processScannedCode的内部逻辑,确认是否存在触发该回调的前置条件。

  2. viewModel.barCodeScannerResult初始值:
    在SHOW_BARCODE_WINDOW分支中,直接判断viewModel.barCodeScannerResult.value.isNotBlank(),若viewModel的该状态初始不为空,会立即执行processScannedCode,进而通过回调修改状态为SHOW_DETAIL。

  3. 添加日志定位状态修改点:
    在所有修改addReturnViewState.value的位置添加日志,打印状态变化和调用栈,精准定位触发源:

    // 示例:修改状态时添加日志
    addReturnViewState.value = SHOW_DETAIL
    Log.d("AddReturnState", "切换到SHOW_DETAIL,调用栈:${Exception().stackTraceToString()}")
    
  4. Composable重组的副作用:
    确认viewModel中是否有其他状态变化导致Composable频繁重组,进而触发了未预期的状态修改逻辑。

内容的提问来源于stack exchange,提问作者jonathan3087

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 03:39:52