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

LaunchedEffect随机执行致TextField重置,请求代码问题排查

问题排查与修复方案

核心问题分析

你遇到的LaunchedEffect随机执行导致TextField重置的问题,根源在于两点:

  1. LaunchedEffect(Unit)的触发条件过于宽泛——Unit是固定值,理论上只会在Composable首次进入组合时执行,但如果拍照/选图操作触发了Activity配置变化(即使锁了竖屏,部分设备拍照仍会触发临时配置变更)或Composable因导航栈/父组件重组被重新创建,LaunchedEffect会再次执行,覆盖用户已输入的内容。
  2. 初始化逻辑放在Composable层面而非ViewModel内部,依赖Composable生命周期,可靠性不足。

具体修复步骤

1. 优化LaunchedEffect的触发条件(快速临时修复)

将LaunchedEffect的key从Unit改为导航参数和SharedViewModel的关键标识,确保只有当初始化数据源变化时才执行初始化:

@Composable
fun AddItemScreen(
    navController: NavHostController,
    addShoppingListItemScreenViewModel: AddShoppingListItemScreenViewModel,
    sharedViewModel: SharedViewModel,
    name: String = "",
    category: String = "",
) {
    val shoppingListItem = sharedViewModel.item.value

    // 仅当name、category或待编辑项的id变化时,才重新执行初始化
    LaunchedEffect(name, category, shoppingListItem.id) {
        if (name.isNotEmpty() && category.isNotEmpty()) {
            addShoppingListItemScreenViewModel.setStateValue("Name", name)
            addShoppingListItemScreenViewModel.setStateValue("Category", category)
        } else {
            addShoppingListItemScreenViewModel.setStateValue(
                "ShoppingListItem",
                shoppingListItem
            )
        }
    }
    // ... 其余代码
}

2. 将初始化逻辑移至ViewModel(推荐长期方案)

把初始化逻辑从Composable迁移到ViewModel内部,利用ViewModel的生命周期(不受Composable重组/Activity重建影响)来管理状态,彻底避免依赖LaunchedEffect的问题:

修改AddShoppingListItemScreenViewModel

class AddShoppingListItemScreenViewModel(
    private val savedStateHandle: SavedStateHandle,
    private val sharedViewModel: SharedViewModel
) : ViewModel() {
    // 用SavedStateHandle绑定状态,确保Activity重启时不丢失
    private val _name = savedStateHandle.getStateFlow("name", "").collectAsState()
    val name: State<String> = _name

    private val _category = savedStateHandle.getStateFlow("category", "").collectAsState()
    val category: State<String> = _category

    init {
        // 从导航参数获取初始化数据
        val navName = savedStateHandle.get<String>("name") ?: ""
        val navCategory = savedStateHandle.get<String>("category") ?: ""

        if (navName.isNotEmpty() && navCategory.isNotEmpty()) {
            savedStateHandle["name"] = navName
            savedStateHandle["category"] = navCategory
        } else {
            // 从SharedViewModel获取待编辑项数据
            val item = sharedViewModel.item.value
            savedStateHandle["name"] = item.name
            savedStateHandle["category"] = item.category
        }
    }

    fun setStateValue(key: String, value: Any?) {
        when(key) {
            "Name" -> savedStateHandle["name"] = value as String
            "Category" -> savedStateHandle["category"] = value as String
            "ShoppingListItem" -> {
                val item = value as ShoppingListItem
                savedStateHandle["name"] = item.name
                savedStateHandle["category"] = item.category
            }
        }
    }
}

修改NavHost中的AddItemScreen参数传递

不再手动传递导航参数,让ViewModel通过SavedStateHandle自动获取:

composable(
    route = NavScreens.AddItemScreen.route + "?id={id}&name={name}&category={category}",
    arguments = listOf(
        navArgument("id") {
            type = NavType.LongType
            defaultValue = 0L
        },
        navArgument("name") {
            type = NavType.StringType
            defaultValue = ""
        },
        navArgument("category") {
            type = NavType.StringType
            defaultValue = ""
        }
    )
) { entry ->
    val addShoppingListScreenviewModel: AddShoppingListItemScreenViewModel = hiltViewModel()
    AddItemScreen(
        navController = navController,
        addShoppingListItemScreenViewModel = addShoppingListScreenviewModel,
        sharedViewModel = sharedViewModel
    )
}

简化AddItemScreen代码

移除多余的参数和LaunchedEffect:

@Composable
fun AddItemScreen(
    navController: NavHostController,
    addShoppingListItemScreenViewModel: AddShoppingListItemScreenViewModel,
    sharedViewModel: SharedViewModel,
) {
    val name = addShoppingListItemScreenViewModel.name.value
    val category = addShoppingListItemScreenViewModel.category.value

    // ... 直接使用name和category渲染TextField,处理用户输入
}

3. 额外注意事项

  • 确保拍照/选图的逻辑不会意外销毁当前Composable(比如避免用finish()或错误的导航操作)。
  • 如果使用第三方图片选择库,检查是否会触发Activity配置变化,必要时在Manifest中为Activity添加android:configChanges="orientation|screenSize|smallestScreenSize"属性,避免Activity重启。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 13:15:27