跨Fragment传值时Shared MutableLiveData值为null的问题排查
核心原因
ViewModel实例独立,初始值为null
每个Fragment通过by viewModels()获取的WeatherScreenViewModel都是独立实例,天气Fragment每次被创建时,新ViewModel的city2默认值为null。switchMap会在city2的任何值变化(包括初始的null值)时触发,导致第一次以null为参数执行请求逻辑,随后调用setCity()又触发一次正常请求,最终重复获取数据。参数处理逻辑的日志误导
从主页跳转时,日志是在setCity()之后打印,所以能看到city2有值;而从收藏Fragment跳转时,先打印未赋值的city2,所以显示为null——这只是日志顺序导致的表象,本质问题是ViewModel初始化时的默认null值触发了额外请求。
解决方案
方案1:规避null触发无效请求
修改ViewModel中city2的初始值,并在switchMap中增加有效性判断:
// ViewModel中修改city2的定义 val city2 = MutableLiveData<String>("") // 修改switchMap逻辑 val transformationWeather = city2.switchMap { cityName -> liveData(viewModelScope.coroutineContext + Dispatchers.IO) { if (cityName.isNullOrEmpty()) { emit(Resource.Error("Invalid city name")) return@liveData } val geoResult = geoCoderRepository.cityToLongLat(cityName) if (geoResult.status is Success) { val list = listOf( geoResult.status.data!!.lat, geoResult.status.data!!.long!! ) val notNullList: List<Double> = list.filterNotNull() emit(weatherRepository.fetchWeather(notNullList)) } } }
这样ViewModel初始化时city2是空字符串,switchMap会触发但直接返回错误,不会执行无效请求;当调用setCity()传入有效城市名时,才触发正常请求,避免重复。
方案2:使用Activity级共享ViewModel
如果需要跨Fragment共享城市状态,将所有Fragment的ViewModel获取方式改为by activityViewModels(),让ViewModel作用域提升到Activity级别,所有Fragment共享同一个实例:
// 主页Fragment、收藏Fragment、天气Fragment统一修改 private val viewModel : WeatherScreenViewModel by activityViewModels()
这种方式会保留ViewModel状态直到Activity销毁,适合需要跨页面共享状态的场景。
方案3:优化天气Fragment参数处理逻辑
统一参数获取逻辑,避免分支代码导致的混淆:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val targetCity = arguments?.getString("city") ?: arguments?.getString("fav_city") targetCity?.let { city = it viewModel.setCity(it) Log.d("target_city", it) } ?: run { // 无有效城市名时的处理逻辑 } }
关于单独为city创建ViewModel的可行性
完全可行,本质是拆分状态管理职责。如果项目中城市状态需要在多个页面共享,单独抽离一个CitySharedViewModel(使用Activity级别作用域)能让代码职责更清晰,避免WeatherScreenViewModel承担过多状态管理工作。
内容的提问来源于stack exchange,提问作者Roni

