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

Jetpack Compose天气应用LaunchedEffect重复执行问题求助

解决Jetpack Compose中返回页面时未修改温度单位却触发数据刷新的问题

你的问题出在:当从其他页面返回时,HomeScreen可能被重新组合(甚至销毁后重建),此时LaunchedEffect(userPrefs.tempUnit)会因为首次进入当前组合树而执行,哪怕温度单位没有变化。如果userPrefs是每次重组都会生成新实例的对象,即使tempUnit值相同,也可能被LaunchedEffect判定为key变化(若tempUnit是Int这类基本类型则不会出现此情况)。

下面提供两种可行的解决方案:

方案一:在ViewModel中维护当前单位,避免重复请求

在ViewModel中添加变量记录当前使用的WeatherUnit,仅当传入的单位与记录值不一致时,才执行数据加载逻辑,从根源上避免无效请求。

修改后的ViewModel代码:

@HiltViewModel
class HomeViewModel @Inject constructor(
    private val getWeather: GetWeatherUseCase,
    private val getCurrentLocation: GetCurrentLocationUseCase,
) : ViewModel() {

    data class UIState(
        val data: ForecastWeather? = null,
        val exception: Exception? = null
    )

    private val _state = MutableStateFlow(UIState())
    val state: StateFlow<UIState> = _state.asStateFlow()

    // 记录当前正在使用的温度单位
    private var currentUnit: WeatherUnit? = null

    fun loadWeatherData(unit: WeatherUnit) {
        // 单位未变化时直接返回,不执行请求
        if (currentUnit == unit) return
        currentUnit = unit

        viewModelScope.launch {
            when (val locResult = getCurrentLocation()) {
                is DataResult.Success -> {
                    getWeatherData(locResult.data, unit)
                }
                is DataResult.Failed -> {
                    _state.update { it.copy(exception = locResult.exception) }
                }
            }
        }
    }

    private suspend fun getWeatherData(location: Location, unit: WeatherUnit) {
        when (val result = getWeather.invoke(location, unit)) {
            is DataResult.Success -> {
                _state.update { it.copy(data = result.data) }
            }
            is DataResult.Failed -> {
                _state.value = UIState(
                    data = result.data,
                    exception = result.exception
                )
            }
        }
    }
    
}

方案二:在Composable中用remember记录历史单位,控制触发时机

在HomeScreen中使用remember保存之前的温度单位值,仅当新单位与历史值不同时,才触发数据加载逻辑。

修改后的HomeScreen代码:

@Composable
fun HomeScreen(
    userPrefs: UserPrefs,
    onMenuClicked: () -> Unit,
    onNext7DaysClicked: (List<DailyWeather>) -> Unit,
    modifier: Modifier = Modifier,
) {

    val viewModel = hiltViewModel<HomeViewModel>()
    val state by viewModel.state.collectAsState()

    // 用remember持久化记录上一次的温度单位索引
    val previousTempUnit = remember { mutableStateOf(userPrefs.tempUnit) }

    LaunchedEffect(userPrefs.tempUnit) {
        // 单位未变化时直接退出,不执行请求
        if (previousTempUnit.value == userPrefs.tempUnit) return@LaunchedEffect
        
        previousTempUnit.value = userPrefs.tempUnit
        val selectedTempUnit = TemperatureUnit.entries[userPrefs.tempUnit]
        val weatherUnit = if (selectedTempUnit == TemperatureUnit.CELSIUS) WeatherUnit.METRIC else WeatherUnit.IMPERIAL
        viewModel.loadWeatherData(weatherUnit)
    }
    // 其余代码不变
}

两种方案对比:

  • 方案一将逻辑统一放在ViewModel中,符合单一职责原则,无论Composable如何重组,ViewModel都会自行判断是否需要发起请求,适合复杂场景。
  • 方案二则在UI层直接控制触发时机,实现简单,适合小型项目或逻辑简单的页面。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 04:25:24