Repository navigation
fix: refresh habits screen state when returning after midnight - #31
Merged
Merged
Conversation
Symptom
-------
Opening the Habits tab before midnight (e.g. at 23:30), leaving the app in the
background without killing the process, and returning after midnight (e.g. at
00:20) left the screen in a stale / hybrid state:
* Header progress (completedToday / totalHabits) and card completion states
still reflected the previous day.
* In NumericHabitCard, the selection dot and value counter stayed anchored on
yesterday even if the card recomposed, and tapping +/- could apply yesterday's
base todayValue to the new date.
* In HabitCard (boolean), the new day could remain treated as a future
(disabled) cell in the weekly calendar or fail to scroll across a Sunday ->
Monday rollover.
Root cause
----------
There was no reactive source of truth for "today" across the Habits feature;
LocalDate.now() was called independently in multiple places:
1. HabitViewModel.init called getHabitsWithStatus() only once. Its default
parameter (date: LocalDate = LocalDate.now()) was evaluated at ViewModel
creation time and stayed captured inside the UseCase flow for the entire
lifetime of the ViewModel. Returning from the background without a DB
emission never re-ran the combine block, and even on a DB emission the
UseCase still computed isCompletedToday, todayValue and streaks against the
captured date from the previous day.
2. NumericHabitCard keyed weekStart and selectedDate only on
remember(weekOffset). Because weekOffset stayed 0 across midnight,
selectedDate never reset to the new day.
3. HabitScreen passed LocalDate.now() directly to incrementNumericHabit /
decrementNumericHabit while reading currentValue = habitWithStatus.todayValue
(computed for the stale date).
4. HabitCard read LocalDate.now() directly during composition and only seeded
rememberWeekCalendarState's firstVisibleWeekDate on initial composition.
Fix
---
Make `today: LocalDate` part of HabitUiState.Success and drive it from a
lifecycle-aware StateFlow in HabitViewModel:
* HabitUiState.kt: add `today: LocalDate` to HabitUiState.Success.
* HabitViewModel.kt: implement DefaultLifecycleObserver and hold a
`_today = MutableStateFlow(LocalDate.now())` updated in onResume via
refreshTodayIfNeeded(). Switch the UseCase subscription with
`_today.flatMapLatest { today -> getHabitsWithStatus(today).map { today to it } }`
so `today` and `habits` always emit together without an intermediate frame
where the date has advanced but the statuses have not.
* HabitScreen.kt: register the ViewModel as a lifecycle observer via
DisposableEffect and pass `state.today` down to HabitCard, NumericHabitCard
and the today increment/decrement callbacks.
* NumericHabitCard.kt: accept `today` as a parameter and include it as a key in
`remember(today)` / `remember(today, weekOffset)` for weekOffset, weekStart
and selectedDate.
* HabitCard.kt: accept `today` as a parameter and scroll the WeekCalendar to
`today` inside a `LaunchedEffect(today)` when the date advances.
* HabitViewModelTest.kt: add unit test covering the midnight rollover via
refreshTodayIfNeeded().
Behavior note
-------------
Because `_today` is a MutableStateFlow, resuming the app on the same calendar
day is a no-op (structural equality check prevents any emission or DB
re-computation). Re-computation only happens when the date has actually changed
while the app was in the background.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
Opening the Habits tab before midnight (e.g. at 23:30), leaving the app in the background without killing the process, and returning after midnight (e.g. at 00:20) left the screen in a stale / hybrid state:
Root cause
There was no reactive source of truth for "today" across the Habits feature; LocalDate.now() was called independently in multiple places:
Fix
Make
today: LocalDatepart of HabitUiState.Success and drive it from a lifecycle-aware StateFlow in HabitViewModel:today: LocalDateto HabitUiState.Success._today = MutableStateFlow(LocalDate.now())updated in onResume via refreshTodayIfNeeded(). Switch the UseCase subscription with_today.flatMapLatest { today -> getHabitsWithStatus(today).map { today to it } }sotodayandhabitsalways emit together without an intermediate frame where the date has advanced but the statuses have not.state.todaydown to HabitCard, NumericHabitCard and the today increment/decrement callbacks.todayas a parameter and include it as a key inremember(today)/remember(today, weekOffset)for weekOffset, weekStart and selectedDate.todayas a parameter and scroll the WeekCalendar totodayinside aLaunchedEffect(today)when the date advances.Behavior note
Because
_todayis a MutableStateFlow, resuming the app on the same calendar day is a no-op (structural equality check prevents any emission or DB re-computation). Re-computation only happens when the date has actually changed while the app was in the background.