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
相关产品推荐
相关产品推荐

