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

Kotlin中如何实现仅首次启动时向Room数据库保存数据?

解决方案

核心问题分析

  1. 重复保存的根源:启动页ViewModel的init块会在每次ViewModel实例化时自动执行insertExchanges(),而每次打开应用都会创建新的ViewModel实例,导致重复插入数据。
  2. SharedPreferences逻辑错误:原checkDate函数先写入当前时间再读取,导致sharedDate永远等于当前时间,isDayPassed条件永远不成立;且逻辑方向错误,你需要的是标记是否完成过首次初始化,而非判断日期差。

1. 修正SharedPreferences检查逻辑

替换原checkDate函数,改为标记首次启动状态,同时支持可选的每日更新逻辑:

fun checkFirstLaunch(context: Context, viewModel: SplashScreenViewModel) {
    val sharedPreferences = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
    val isFirstLaunch = sharedPreferences.getBoolean("is_first_launch", true)

    if (isFirstLaunch) {
        // 首次启动执行初始插入
        viewModel.insertExchanges()
        // 标记为已完成首次启动
        sharedPreferences.edit().putBoolean("is_first_launch", false).apply()
        // 记录首次更新时间,用于后续每日更新判断
        sharedPreferences.edit().putLong("last_update_time", System.currentTimeMillis()).apply()
    } else {
        // 非首次启动,判断是否需要每日更新
        val lastUpdateTime = sharedPreferences.getLong("last_update_time", 0)
        val isNeedDailyUpdate = System.currentTimeMillis() - lastUpdateTime >= TimeUnit.DAYS.toMillis(1)
        if (isNeedDailyUpdate) {
            viewModel.update()
            sharedPreferences.edit().putLong("last_update_time", System.currentTimeMillis()).apply()
        }
    }
}

2. 修改ViewModel,移除自动执行的插入逻辑

删掉ViewModel中的init块,让插入操作由外部检查逻辑触发:

@HiltViewModel
class SplashScreenViewModel @Inject constructor(
    private val insertExchangeUseCase: InsertExchangeUseCase,
    private val updateExchangeUseCase: UpdateExchangeUseCase,
    private val getAllExchangeUseCase: GetAllExchangeUseCase
) : ViewModel() {

    // 改为公开方法,由外部触发初始插入
    fun insertExchanges() {
        viewModelScope.launch {
            insertExchangeUseCase.insert(DEFAULT_CURRENCY)
        }
    }

    fun update() {
        viewModelScope.launch {
            updateExchangeUseCase.updateExchange(getAllExchangeUseCase.get(DEFAULT_CURRENCY))
        }
    }
}

3. 更新启动页UI调用逻辑

在SplashScreen中替换原checkDate调用为修正后的函数:

@Composable
fun SplashScreen(
    navController: NavController,
    viewModel : SplashScreenViewModel = hiltViewModel()
) {
    val context: Context = LocalContext.current
    checkFirstLaunch(context, viewModel)

    // 原有动画与导航逻辑保持不变
    val scale = remember {
        Animatable(0f)
    }

    LaunchedEffect(key1 = true) {
        scale.animateTo(
            targetValue = 0.3f,
            animationSpec = tween(
                durationMillis = 500,
                easing = {
                    OvershootInterpolator(2f).getInterpolation(it)
                }
            )
        )
        delay(2000L)
        navController.navigate(Screen.ExchangeMainScreen.route)
    }

    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Image(
            painter = painterResource(id = R.drawable.ic_currency_exchange_logo_512),
            contentDescription = "Logo"
        )
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 19:50:23