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

Jetpack Compose:remember变量与ViewModel数据更新无法同步至界面

解决Jetpack Compose中repeticiones变量界面同步问题

问题描述

在Jetpack Compose开发中,界面展示的repeticiones(重复次数)变量初始值可从ViewModel的person LiveData正常加载,但调用workoutViewModel.restarRepeticion()减少次数时,界面无任何更新;点击“开始”按钮启动倒计时,尝试通过ViewModel将repeticiones减至0的过程中,界面也无法同步变化,即使移除协程逻辑问题依旧存在。

错误原因分析

  1. ViewModel递减操作逻辑错误:restarRepeticion方法中使用了currentData.repeticiones--(后置递减运算符),该操作会先返回当前值再执行减1,导致新创建的WorkoutData对象并未使用减后的值,LiveData实际没有更新。
  2. Compose本地状态与ViewModel状态不同步:界面中单独维护了repeticiones本地状态,仅在person首次加载时赋值,后续ViewModel更新person LiveData时,循环逻辑依赖的是本地状态而非ViewModel的最新值,导致界面无法响应ViewModel的变化。

修复步骤

1. 修正ViewModel的递减方法

将restarRepeticion中的currentData.repeticiones--替换为currentData.repeticiones - 1,确保使用减后的数值创建新的WorkoutData对象:

fun restarRepeticion(){
    val currentData = _person.value
    if(currentData!=null){
        val updatedData=WorkoutData(currentData.name, currentData.repeticiones - 1)
        _person.value=updatedData
    }
}

2. 移除Compose本地状态,直接依赖ViewModel的LiveData

删除界面中单独维护的repeticiones变量,直接从person的状态中获取重复次数,确保界面始终响应ViewModel的最新数据:

// 移除本地变量
// var repeticiones by remember { mutableIntStateOf(3) }

// 直接从ViewModel的LiveData获取重复次数
val repeticiones = person?.repeticiones ?: 3

3. 调整倒计时循环的判断逻辑

修改“开始”按钮的循环条件,使用ViewModel中的person值而非本地状态,避免状态不一致:

Button(
    onClick = {
        parar = false // 重置停止标记
        coroutineScope.launch {
            while ((person?.repeticiones ?: 0) > 0 && !parar) {
                delay(200)
                if (parar) break
                workoutViewModel.restarRepeticion()
            }
        }
    }
) {
    Text(text = stringResource(R.string.btn_comenzar))
}

修改后的完整代码

Compose界面代码

package com.example.workoutpila.ui.screens

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.workoutpila.viewmodel.WorkoutViewModel
import com.example.workoutpila.R
import androidx.compose.runtime.*
import androidx.compose.ui.res.painterResource
import com.example.workoutpila.navigation.Routes
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@Composable
fun WorkoutScreen(navController: NavController, workoutViewModel: WorkoutViewModel){

    val person by workoutViewModel.person.observeAsState()
    var name by remember {
        mutableStateOf("Cargando...")
    }
    val coroutineScope = rememberCoroutineScope()
    var parar by rememberSaveable {
        mutableStateOf(false)
    }

    person?.let {data->
        name = data.name
    }
    // 直接从ViewModel的LiveData获取重复次数
    val repeticiones = person?.repeticiones ?: 3

    Scaffold(
        bottomBar = {
            BottomAppBar(
                modifier = Modifier.fillMaxWidth()
            ) {
                IconButton(
                    onClick = {
                        navController.navigate(Routes.MainScreen.route)
                    },
                    modifier = Modifier.fillMaxWidth()
                ) {
                    Row(modifier = Modifier.fillMaxWidth()) {
                        Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
                        Spacer(modifier = Modifier.width(20.dp))
                        Text(text = stringResource(R.string.btn_volver))
                    }
                }
            }
        }

    ) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(it)
                .background(MaterialTheme.colorScheme.background),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(10.dp)
            ) {
                Text(
                    text = stringResource(R.string.frase_workout) + " $name",
                    fontWeight = FontWeight.Bold,
                    fontSize = 40.sp,
                    textAlign = TextAlign.Center
                )
            }
            Spacer(modifier = Modifier.height(40.dp))
            Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
                Text(
                    text = stringResource(R.string.repeticiones),
                    fontSize = 30.sp,
                    textAlign = TextAlign.Center
                )
                Spacer(modifier = Modifier.width(8.dp))
                Text(
                    text = repeticiones.toString(),
                    fontWeight = FontWeight.Bold,
                    fontSize = 40.sp,
                    textAlign = TextAlign.Center
                )
            }
            Spacer(modifier = Modifier.height(20.dp))
            Image(
                painter = painterResource(R.drawable.workout_img),
                contentDescription = "Workout",
                modifier = Modifier.size(300.dp)
            )
            Spacer(modifier = Modifier.height(20.dp))
            Button(
                onClick = {
                    parar = false
                    coroutineScope.launch {
                        while ((person?.repeticiones ?: 0) > 0 && !parar) {
                            delay(200)
                            if (parar) break
                            workoutViewModel.restarRepeticion()
                        }
                    }
                }
            ) {
                Text(
                    text = stringResource(R.string.btn_comenzar)
                )
            }
            Spacer(modifier = Modifier.height(20.dp))
            Button(
                onClick = {
                    parar = true
                }
            ) {
                Text(
                    text = stringResource(R.string.btn_parar)
                )
            }
        }
    }
}

ViewModel代码

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.workoutpila.model.WorkoutData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class WorkoutViewModel: ViewModel() {
    private val _person = MutableLiveData<WorkoutData>()
    val person: LiveData<WorkoutData> = _person

    init {
        viewModelScope.launch {
            _person.value = WorkoutData.getData()
        }
    }

    fun setRepeticiones(repeticiones:Int){
        val currentData = _person.value
        if(currentData!=null){
            val updatedData=WorkoutData(currentData.name, repeticiones)
            _person.value=updatedData
        }
    }

    fun setNombre(name:String){
        val currentData = _person.value
        if(currentData!=null){
            val updatedData=WorkoutData(name, currentData.repeticiones)
            _person.value=updatedData
        }
    }

    fun restarRepeticion(){
        val currentData = _person.value
        if(currentData!=null){
            // 修复递减逻辑,使用减后的值创建新对象
            val updatedData=WorkoutData(currentData.name, currentData.repeticiones - 1)
            _person.value=updatedData
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 17:35:52