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

Jetpack Compose EditForm无法自动更新,仅重启Activity生效求助

问题:Jetpack Compose EditForm组件无法自动更新数据

我正在开发基于Jetpack Compose的应用,需通过Compose Navigation从角色列表页跳转至EditForm编辑页,传递角色ID(cID)后查询本地数据库获取角色数据并展示编辑。但目前EditForm组件无法自动更新,TextField初始加载均为null,仅在Android Studio中点击「应用更改并重启Activity」后才能正常显示数据,而ViewModel中的数据已正常持久化。

相关代码片段如下:

class RickAndMortyViewModel : ViewModel() {

    private var _castMember = mutableStateOf<CastMember?>(null)
    val castMember: State<CastMember?> = _castMember

    // Function to update the castMember
    suspend fun updateCastMemberById(cID: Int, db: AppDatabase){
        viewModelScope.launch {
            _castMember.value = withContext(Dispatchers.IO) {
                db.dao.getCastMemberByID(cID)
            }

        }
    }
    fun getCastMember(): CastMember?{
        return castMember.value
    }

}
@Composable
fun CharacterCard(c:CastMember,db: AppDatabase, navController: NavController){
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(2.dp)
            .background(color = androidx.compose.ui.graphics.Color.LightGray)
            .border(
                1.dp,
                color = androidx.compose.ui.graphics.Color.Black,
                shape = RoundedCornerShape(8.dp)
            )
    ){
        Column {
            AsyncImage(
                model = ImageRequest.Builder(
                    LocalContext.current)
                    .data(c.image)
                    .build(),
                contentDescription = c.name
            )
        }
        Column(
            modifier = Modifier
                .padding(5.dp)
        ) {
            Text(text = c.id.toString())
            Text(text = c.name)
            Text(text = "Species: " + c.species)
            Text(text = "Gender: " + c.gender)

            Row(
                modifier= Modifier.padding(start=200.dp),
                horizontalArrangement = Arrangement.SpaceEvenly
            ){
                Image(
                    painter = painterResource(id = R.drawable.ic_insert),
                    contentDescription = "Insert ${c.name}",
                    modifier = Modifier
                        .clickable {
                            CoroutineScope(Dispatchers.IO).launch {
                                db.dao.insertCastMember(c)
                                Log.i("INSERTED:", "${c.name} into database" )
                            }

                        }
                )
                Image(
                    painter = painterResource(id = R.drawable.ic_edit),
                    contentDescription = "Edit ${c.name}",
                    modifier = Modifier
                        .clickable {
                            navController.navigate("edit/${c.id}")
                        }
                )

                Image(
                    painter = painterResource(id = R.drawable.ic_delete),
                    contentDescription = "Delete ${c.name}",
                    modifier = Modifier
                        .clickable {
                            CoroutineScope(Dispatchers.IO).launch {
                                db.dao.deleteCastMember(c)
                                Log.i("DELETED:", "${c.name} from database" )
                            }
                        }
                )
            }
        }
    }
}

@SuppressLint("CoroutineCreationDuringComposition")
@Composable
fun EditForm(cID: Int, db: AppDatabase, viewModel: RickAndMortyViewModel){
    var name by rememberSaveable { mutableStateOf("")}
    var species by rememberSaveable { mutableStateOf("")}
    var gender by rememberSaveable { mutableStateOf("")}

    Log.i("cID", cID.toString())

    LaunchedEffect(cID){
        viewModel.updateCastMemberById(cID,db)
    }

    name = viewModel.getCastMember()?.name.toString()

    Column {
        Log.i("CastMember", "")

        TextField(
            value = name,
            onValueChange = {
                name = it
            },
            label={
                Text("Name")
            }
        )

        TextField(
            value = viewModel.getCastMember()?.species.toString(),
            onValueChange = {
                species = it
            },
            label={
                Text("Species")
            }
        )
        TextField(
            value = viewModel.getCastMember()?.gender.toString(),
            onValueChange = {
                gender = it
            },
            label={
                Text("Gender")
            }
        )
        Button(
            onClick = { /*TODO*/ }
        ) {
            Text("Update")
        }
    }
}
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            RickAndMortyChallengeTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    val context: Context = LocalContext.current
                    val rickAndMortyManager: RickAndMortyManager = RickAndMortyManager()
                    val db = AppDatabase.getInstance(context)
                    val navController = rememberNavController()

                    NavHost(navController = navController, startDestination = Destination.Home.route ){
                        composable(Destination.Home.route){
                            CharacterScreen(rickAndMortyManager = rickAndMortyManager, db = db, navController =  navController)
                        }
                        composable("edit/{cID}"){ backStackEntry ->
                            var str_cID: String? = backStackEntry.arguments?.getString("cID") ?: "nil"
                            var cID = str_cID?.toInt()

                            if (cID != null) {
                                EditForm(cID, db, viewModel = RickAndMortyViewModel())
                            }
                        }
                    }
                }
            }
        }
    }
}
@Dao
interface RickAndMortyDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertCastMember(c: CastMember)

    @Delete()
    fun deleteCastMember(c: CastMember)

    @Query("SELECT * FROM tbl_cast_members WHERE id = :cID")
    fun getCastMemberByID(cID: Int): CastMember
}

解决方案

1. 修复ViewModel实例化方式

当前每次导航到编辑页都会新建RickAndMortyViewModel实例,导致状态无法跨重组保留。改用Compose内置的viewModel()函数获取实例,确保其生命周期与页面绑定:

// 修改MainActivity中的导航路由代码
composable("edit/{cID}"){ backStackEntry ->
    val str_cID: String? = backStackEntry.arguments?.getString("cID") ?: "nil"
    val cID = str_cID?.toInt()
    val viewModel: RickAndMortyViewModel = viewModel() // 用viewModel()获取实例,而非手动新建
    if (cID != null) {
        EditForm(cID, db, viewModel = viewModel)
    }
}

2. 响应式同步UI状态与ViewModel数据

原EditForm中的输入框变量仅在初始重组时赋值,未监听castMember的变化。通过collectAsState()观察ViewModel状态,并在数据更新时同步UI:

// 修改EditForm组件
@Composable
fun EditForm(cID: Int, db: AppDatabase, viewModel: RickAndMortyViewModel){
    // 观察ViewModel中的castMember状态变化
    val castMember by viewModel.castMember.collectAsState()

    var name by rememberSaveable { mutableStateOf("")}
    var species by rememberSaveable { mutableStateOf("")}
    var gender by rememberSaveable { mutableStateOf("")}

    // 当castMember数据更新时,同步输入框初始值
    LaunchedEffect(castMember) {
        castMember?.let {
            name = it.name
            species = it.species
            gender = it.gender
        }
    }

    // 当cID变化时触发数据库查询
    LaunchedEffect(cID){
        viewModel.updateCastMemberById(cID, db)
    }

    Column {
        TextField(
            value = name,
            onValueChange = { name = it },
            label = { Text("Name") }
        )

        TextField(
            value = species,
            onValueChange = { species = it },
            label = { Text("Species") }
        )
        TextField(
            value = gender,
            onValueChange = { gender = it },
            label = { Text("Gender") }
        )
        Button(onClick = { /*TODO*/ }) {
            Text("Update")
        }
    }
}

3. 优化ViewModel协程逻辑

简化updateCastMemberById的协程嵌套,避免不必要的viewModelScope.launch:

class RickAndMortyViewModel : ViewModel() {

    private var _castMember = mutableStateOf<CastMember?>(null)
    val castMember: State<CastMember?> = _castMember

    suspend fun updateCastMemberById(cID: Int, db: AppDatabase){
        _castMember.value = withContext(Dispatchers.IO) {
            db.dao.getCastMemberByID(cID)
        }
    }
}

4. 清理冗余代码

  • 删除EditForm中的@SuppressLint("CoroutineCreationDuringComposition")注解(现在用LaunchedEffect管理协程,符合Compose规范)
  • 移除RickAndMortyViewModel中的getCastMember()函数,直接通过castMember变量暴露状态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 10:05:03