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

Kotlin/Android Studio饮水追踪应用连续打卡成就未更新求助

饮水追踪应用连续达标成就未划掉问题排查与修复

问题背景

我们使用Kotlin/Android Studio开发的饮水追踪应用中,连续达标X天的成就无法正确显示划掉状态。以下是相关核心代码:

AchievementsFragment.kt 核心代码

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    _binding = FragmentAchievementsBinding.inflate(inflater, container, false)
    sharedPreferences = requireActivity().getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    val database = AppDatabase.getDatabase(requireContext())
    val waterDao = database.waterDao()
    val dailyWaterGoal = sharedPreferences.getInt("dailyGoal", 2000)

    binding.infoButton.setOnClickListener { showInfoDialog() }

    viewLifecycleOwner.lifecycleScope.launch {
        val lastDayIntake = waterDao.getLastDayData()
        var streak = sharedPreferences.getInt("currentStreak", 0)

        if (lastDayIntake?.amount ?: 0 >= dailyWaterGoal) {
            streak++
            sharedPreferences.edit().putInt("currentStreak", streak).apply()
        } else {
            streak = 0
            sharedPreferences.edit().putInt("currentStreak", 0).apply()
        }
        updateAchievements(streak)
    }
}

private fun updateAchievements(numDays: Int) {
    updateAchievement(numDays, 3, binding.textAchievement1)
    updateAchievement(numDays, 5, binding.textAchievement2)
    updateAchievement(numDays, 10, binding.textAchievement3)
    updateAchievement(numDays, 30, binding.textAchievement4)
    updateAchievement(numDays, 180, binding.textAchievement5)
}

private fun updateAchievement(numDays: Int, requiredDays: Int, textView: TextView) {
    if (numDays >= requiredDays) {
        textView.paintFlags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
    } else {
        textView.paintFlags = textView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
    }
}

WaterDao.kt 核心代码

interface WaterDao {
    @Query("SELECT * FROM WaterIntake ORDER BY day, month, year DESC LIMIT 1")
    suspend fun getLastDayData(): WaterIntake?
}

data class AmountSum(
    val date: Int,
    val totalAmount: Int
)

核心问题分析

  1. 日期排序逻辑错误
    WaterDao中getLastDayData的SQL排序规则错误,ORDER BY day, month, year DESC会优先按日排序,导致无法正确获取最新日期的饮水记录(比如12月31日会被排在1月1日之后)。

  2. 连续天数(streak)计算逻辑漏洞

    • 未判断最后一条记录的日期是否为当天:用户昨天达标,今天未喝水时,代码会读取昨天的达标记录并累加streak,导致连续天数计算错误。
    • 重复触发累加:用户同一天多次进入成就页面时,每次都会触发streak累加,造成连续天数虚高。
  3. 成就状态未实时更新
    仅在Fragment创建时计算一次streak并更新成就,用户添加饮水后不会自动刷新成就状态,导致达标后成就仍未划掉。

  4. 视图刷新不及时
    修改paintFlags后未强制刷新TextView,部分场景下划掉状态无法立即生效。

修复方案

1. 修复日期排序SQL

修改WaterDao中的查询语句,按年、月、日的优先级降序排序,确保获取最新日期的记录:

interface WaterDao {
    // 修正排序规则
    @Query("SELECT * FROM WaterIntake ORDER BY year DESC, month DESC, day DESC LIMIT 1")
    suspend fun getLastDayData(): WaterIntake?
    
    // 添加Flow监听,用于实时更新
    @Query("SELECT * FROM WaterIntake ORDER BY year DESC, month DESC, day DESC LIMIT 1")
    fun getLastDayDataFlow(): Flow<WaterIntake?>
}

2. 重构连续天数计算逻辑

添加日期校验和更新标记,避免重复计算和跨天错误:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    val database = AppDatabase.getDatabase(requireContext())
    val waterDao = database.waterDao()
    val dailyWaterGoal = sharedPreferences.getInt("dailyGoal", 2000)

    binding.infoButton.setOnClickListener { showInfoDialog() }

    // 用Flow监听数据变化,实时更新成就
    viewLifecycleOwner.lifecycleScope.launch {
        waterDao.getLastDayDataFlow().collect { lastDayIntake ->
            val calendar = Calendar.getInstance()
            val currentYear = calendar.get(Calendar.YEAR)
            val currentMonth = calendar.get(Calendar.MONTH) + 1 // Calendar月份从0开始,需+1
            val currentDay = calendar.get(Calendar.DAY_OF_MONTH)

            var streak = sharedPreferences.getInt("currentStreak", 0)
            val lastUpdateYear = sharedPreferences.getInt("lastStreakUpdateYear", -1)
            val lastUpdateMonth = sharedPreferences.getInt("lastStreakUpdateMonth", -1)
            val lastUpdateDay = sharedPreferences.getInt("lastStreakUpdateDay", -1)

            // 判断最后一条记录是否是当天
            val isTodayRecord = lastDayIntake?.run { 
                year == currentYear && month == currentMonth && day == currentDay 
            } ?: false

            if (isTodayRecord) {
                // 当天有记录,且未更新过streak
                if (lastUpdateYear != currentYear || lastUpdateMonth != currentMonth || lastUpdateDay != currentDay) {
                    streak = if (lastDayIntake.amount >= dailyWaterGoal) streak + 1 else 0
                    sharedPreferences.edit()
                        .putInt("currentStreak", streak)
                        .putInt("lastStreakUpdateYear", currentYear)
                        .putInt("lastStreakUpdateMonth", currentMonth)
                        .putInt("lastStreakUpdateDay", currentDay)
                        .apply()
                }
            } else {
                // 当天无记录,检查昨天是否达标,若达标则重置streak(视为当天未达标)
                val yesterdayCalendar = Calendar.getInstance().apply { add(Calendar.DAY_OF_MONTH, -1) }
                val yesterdayYear = yesterdayCalendar.get(Calendar.YEAR)
                val yesterdayMonth = yesterdayCalendar.get(Calendar.MONTH) + 1
                val yesterdayDay = yesterdayCalendar.get(Calendar.DAY_OF_MONTH)

                val isYesterdayRecord = lastDayIntake?.run {
                    year == yesterdayYear && month == yesterdayMonth && day == yesterdayDay
                } ?: false

                streak = if (isYesterdayRecord && lastDayIntake.amount >= dailyWaterGoal) 0 else 0
                sharedPreferences.edit().putInt("currentStreak", streak).apply()
            }

            updateAchievements(streak)
        }
    }
}

3. 修复视图刷新问题

在updateAchievement方法中添加强制刷新,确保划掉状态立即生效:

private fun updateAchievement(numDays: Int, requiredDays: Int, textView: TextView) {
    if (numDays >= requiredDays) {
        textView.paintFlags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
    } else {
        textView.paintFlags = textView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
    }
    textView.invalidate() // 强制刷新视图
}

验证建议

  1. 测试跨天场景:昨天达标,今天未喝水,检查streak是否重置为0,对应成就未划掉。
  2. 测试当天达标场景:添加饮水至达标,返回成就页面,检查对应成就是否立即划掉。
  3. 测试重复进入场景:同一天多次进入成就页面,检查streak是否不会重复累加。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 06:34:56