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

基于Firebase的习惯追踪应用:连续完成习惯Streak计算实现问询

实现Firebase习惯追踪App的连续完成天数(Streak)计算

核心逻辑概述

由于Firebase仅保存用户勾选过的DailyActivity记录,且列表已按日期降序排列(最新日期在前),我们需要从最新记录开始,逐个验证:

  1. 当前记录是否为完成状态(done=true)
  2. 当前记录的日期是否与上一条记录的日期连续(即当前日期是上一条日期的前一天)
    一旦遇到未完成记录或日期中断,立即停止计数,统计符合条件的总天数。

步骤1:编写日期工具方法

先实现日期转换和连续日期判断的工具类,简化后续逻辑:

object DateUtils {
    // 将"yyyy-MM-dd"格式的字符串转为LocalDate
    fun stringToLocalDate(dateStr: String): LocalDate {
        return LocalDate.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE)
    }

    // 获取指定日期的前一天
    fun getPreviousDay(date: LocalDate): LocalDate {
        return date.minusDays(1)
    }
}

步骤2:在ViewModel中实现Streak计算逻辑

假设ViewModel已持有降序排列的dailyActivities列表,添加计算方法:

class HabitDetailsViewModel : ViewModel() {
    // 已获取的降序DailyActivity列表
    val dailyActivities: LiveData<List<DailyActivity>> = ...

    // 计算当前连续完成天数
    fun calculateCurrentStreak(): Int {
        val activities = dailyActivities.value ?: return 0
        if (activities.isEmpty()) return 0

        var streakCount = 0
        var lastValidDate: LocalDate? = null

        for (activity in activities) {
            // 遇到未完成记录,直接终止计数
            if (!activity.done) break

            val currentDate = DateUtils.stringToLocalDate(activity.date)
            
            if (lastValidDate == null) {
                // 第一条有效记录(最新完成日),计数+1
                streakCount++
                lastValidDate = currentDate
            } else {
                // 验证当前日期是否是上一条有效日期的前一天
                val expectedPrevDate = DateUtils.getPreviousDay(lastValidDate)
                if (currentDate == expectedPrevDate) {
                    streakCount++
                    lastValidDate = currentDate
                } else {
                    // 日期中断,终止计数
                    break
                }
            }
        }

        return streakCount
    }
}

步骤3:在HabitDetailsActivity中调用并更新UI

观察dailyActivities数据变化,计算并显示连续天数:

class HabitDetailsActivity : AppCompatActivity() {
    private lateinit var viewModel: HabitDetailsViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_habit_details)

        viewModel = ViewModelProvider(this)[HabitDetailsViewModel::class.java]

        viewModel.dailyActivities.observe(this) {
            val currentStreak = viewModel.calculateCurrentStreak()
            // 更新UI显示连续天数
            streakTextView.text = getString(R.string.current_streak_format, currentStreak)
        }
    }
}

关键逻辑验证

  • 示例场景:仅存在一条2023-12-28的完成记录,计算结果为1,符合预期。
  • 日期中断场景:若记录为2023-12-28(完成)、2023-12-26(完成),则计数到第一条后,第二条日期不连续,最终streak为1。
  • 未完成中断场景:若记录为2023-12-28(完成)、2023-12-27(未完成),则遍历到第二条时直接终止,streak为1。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 11:01:32