Jetpack Compose离线模式展示最新数据异常:默认值覆盖问题修复
问题描述
我开发了一款天气应用,数据来自REST API,希望用户在离线模式下仍能在界面上查看最后获取的数据,因此使用Jetpack DataStore进行数据的保存与读取。
我订阅了LiveData并尝试将其状态保存到Jetpack DataStore中,数据看似保存正常,但在离线模式下打开应用时,保存函数会再次执行,将默认值0.0覆盖原有数据,请问该如何修复此问题?
相关代码
DataStore 实现类
class SaveDataStore(private val context: Context) { // 确保单例 companion object { private val Context.dataStore: DataStore<Preferences> by preferencesDataStore("temp_C") val TEMP_C = stringPreferencesKey("temp_C") } // 保存温度到DataStore suspend fun save(name: String) { context.dataStore.edit { preferences -> preferences[TEMP_C] = name } } // 获取保存的温度 val getTempC: Flow<String?> = context.dataStore.data .map { preferences -> preferences[TEMP_C] ?: "!!!" } }
Compose 函数代码
// 协程作用域 val scope = rememberCoroutineScope() // 实例化SaveDataStore val dataStore = SaveDataStore(context) val mainScreenViewModel = hiltViewModel<HomeViewModel>() val state by mainScreenViewModel.stateMain.collectAsState() val stateNew = state.tempC.toString() fun initSave() { scope.launch { dataStore.save(stateNew) } } initSave() val savedData = dataStore.getTempC.collectAsState(initial = "") fun result(state: String, savedState: String): String { return if (state.toDouble() != 0.0) state else savedState }
问题根源
核心问题是Compose函数每次重组都会无条件调用initSave():
- 在线时,
state.tempC是API返回的有效温度,保存操作没问题; - 离线时,API请求失败,
state.tempC用默认值0.0,此时initSave()再次执行,直接把DataStore里之前存的有效数据覆盖成0.0。
另外,initSave()没有任何判断逻辑,哪怕是屏幕旋转这类普通重组,都会触发重复保存,完全没必要。
修复方案
方案1:把保存逻辑移到ViewModel,仅在API返回有效数据时执行
这是更合理的架构,ViewModel负责数据的获取与持久化,Compose只做UI展示。
- 让ViewModel持有DataStore实例(通过Hilt注入更规范):
class HomeViewModel @Inject constructor( private val saveDataStore: SaveDataStore, private val weatherRepo: WeatherRepository ) : ViewModel() { private val _stateMain = MutableStateFlow(WeatherState()) val stateMain = _stateMain.asStateFlow() fun fetchWeather() { viewModelScope.launch { val weatherData = weatherRepo.getWeather() if (weatherData != null && weatherData.tempC != 0.0) { // 只有拿到有效数据才保存 saveDataStore.save(weatherData.tempC.toString()) _stateMain.value = weatherData } else { // 离线/请求失败时,从DataStore读历史数据更新UI val savedTemp = saveDataStore.getTempC.first() if (savedTemp != "!!!" && savedTemp.isNotEmpty()) { _stateMain.value = WeatherState(tempC = savedTemp.toDouble()) } // 无历史数据时可提示用户,或保留默认值 } } } }
- Compose中移除保存逻辑,只负责展示:
val mainScreenViewModel = hiltViewModel<HomeViewModel>() val state by mainScreenViewModel.stateMain.collectAsState() // 直接用ViewModel处理后的状态展示 val displayTemp = if (state.tempC != 0.0) { state.tempC.toString() } else { "暂无历史数据" }
方案2:在Compose中添加判断,避免保存默认值
如果一定要在Compose里处理保存,用LaunchedEffect控制触发时机,加上有效值判断:
val scope = rememberCoroutineScope() val dataStore = SaveDataStore(context) val mainScreenViewModel = hiltViewModel<HomeViewModel>() val state by mainScreenViewModel.stateMain.collectAsState() val stateNew = state.tempC.toString() // 仅当tempC变化且为有效值时,才执行保存 LaunchedEffect(state.tempC) { if (state.tempC != 0.0) { dataStore.save(stateNew) } } val savedData = dataStore.getTempC.collectAsState(initial = "") fun result(state: String, savedState: String): String { return if (state.toDouble() != 0.0) state else savedState }
LaunchedEffect(state.tempC)能保证只有state.tempC变化时才执行代码块,再加上state.tempC != 0.0的判断,就不会把默认值存进去了。
额外优化建议
- 别在Compose里直接创建
SaveDataStore,用Hilt注入保证单例; - 温度值建议用Double类型存储(而非String),避免类型转换错误,可以自定义Preferences序列化器:
object DoubleTempSerializer : PreferencesSerializer<Double> { override val key: PreferencesKey<Double> = doublePreferencesKey("temp_C") override fun serialize(value: Double): Double = value override fun deserialize(value: Double): Double = value } // 修改SaveDataStore对应方法 val getTempC: Flow<Double> = context.dataStore.data .map { preferences -> preferences[DoubleTempSerializer.key] ?: 0.0 } suspend fun save(temp: Double) { context.dataStore.edit { preferences -> preferences[DoubleTempSerializer.key] = temp } }
内容的提问来源于stack exchange,提问作者Dmitry Sergienko
相关产品推荐
相关产品推荐

