-
Notifications
You must be signed in to change notification settings - Fork 224
Description
📱 Description & Context
In alarm_challenge_controller.dart, the wake-up challenge timer is managed by the _startTimer() method. This method uses an asynchronous for loop with await Future.delayed() to slowly decrement the progress.value over 15 seconds.
However, a critical concurrency bug occurs when the user interacts with the challenge (e.g., shaking the phone or answering a math question). These actions trigger restartTimer(), which resets the progress to 1.0 and immediately calls _startTimer() again.
Because the previous asynchronous for loop is never explicitly cancelled or broken out of, a completely new loop is spawned alongside the old one.
⚠️ Actual Behavior (The Bug)
If a user shakes the phone 5 times, there will be 5 separate async loops running concurrently in the background. All of these loops are fighting to decrement the exact same progress.value simultaneously. This causes the progress bar to drain exponentially faster with every interaction, instantly dropping to zero and unfairly forcing the user to fail the challenge.
✨ Expected Behavior
When restartTimer() is invoked, any previously running instances of the _startTimer() loop must be terminated before the new loop begins. Only one asynchronous timer loop should be actively decrementing the progress value at any given time.
🔄 Steps to Reproduce
- Set an alarm with the "Shake" or "Math" challenge enabled.
- Wait for the alarm to ring and trigger the challenge screen.
- Rapidly shake the device (or quickly enter math digits) multiple times.
- Observe the progress bar at the bottom of the screen. Instead of resetting and draining slowly over 15 seconds, it will accelerate and drain in a fraction of a second.
💡 Proposed Solution
Introduce a cancellation token pattern (or a session ID integer) at the controller scope.
- Increment the session ID inside
restartTimer(). - Inside the
_startTimer()loop, check if the local loop's ID matches the global session ID. If it does not, explicitlybreakout of the orphaned loop.