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

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展示。

  1. 让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())
                }
                // 无历史数据时可提示用户,或保留默认值
            }
        }
    }
}
  1. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 22:39:17