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 )
核心问题分析
日期排序逻辑错误
WaterDao中getLastDayData的SQL排序规则错误,ORDER BY day, month, year DESC会优先按日排序,导致无法正确获取最新日期的饮水记录(比如12月31日会被排在1月1日之后)。连续天数(streak)计算逻辑漏洞
- 未判断最后一条记录的日期是否为当天:用户昨天达标,今天未喝水时,代码会读取昨天的达标记录并累加streak,导致连续天数计算错误。
- 重复触发累加:用户同一天多次进入成就页面时,每次都会触发streak累加,造成连续天数虚高。
成就状态未实时更新
仅在Fragment创建时计算一次streak并更新成就,用户添加饮水后不会自动刷新成就状态,导致达标后成就仍未划掉。视图刷新不及时
修改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() // 强制刷新视图 }
验证建议
- 测试跨天场景:昨天达标,今天未喝水,检查streak是否重置为0,对应成就未划掉。
- 测试当天达标场景:添加饮水至达标,返回成就页面,检查对应成就是否立即划掉。
- 测试重复进入场景:同一天多次进入成就页面,检查streak是否不会重复累加。
内容的提问来源于stack exchange,提问作者user15991497
相关产品推荐
相关产品推荐

