Reputation: 21
I'm developing a Flutter application with multiple simultaneous timers. These timers need to keep running correctly even when the user:
Navigates away from and back to the page Closes and reopens the app Locks and unlocks the device screen
I've implemented a solution using SharedPreferences
to persist the state and AppLifecycleState
to manage transitions, but I'm facing an issue: with each screen lock/unlock cycle, the timers lose a few milliseconds and gradually fall behind.
Currently, the only way to correct this drift is to leave and return to the screen, but this isn't a viable solution for end users.
class TimerController {
final String id;
bool _isRunning = false;
int elapsedSeconds = 0;
DateTime _lastStartTime;
Timer timer;
final Function(int elapsedSeconds) onTick;
TimerController({@required this.id, @required this.onTick});
Future<void> loadState() async {
final prefs = await SharedPreferences.getInstance();
final savedRunningState = prefs.getBool('${id}_running') ?? false;
final savedElapsedSeconds = prefs.getInt('${id}_elapsed_seconds') ?? 0;
final savedStartTime = prefs.getString('${id}_start_time');
if (savedRunningState && savedStartTime != null) {
final startTime = DateTime.parse(savedStartTime);
final currentTime = DateTime.now();
final totalElapsedTime = currentTime.difference(startTime).inSeconds;
elapsedSeconds = totalElapsedTime;
_isRunning = true;
startTimer();
} else {
elapsedSeconds = savedElapsedSeconds;
}
onTick(elapsedSeconds);
}
void startTimer() {
timer = Timer.periodic(
Duration(seconds: 1),
(_) {
elapsedSeconds++;
onTick(elapsedSeconds);
_saveState();
},
);
}
}
// In the main page
void _handleScreenLock() {
_lastPauseTime = DateTime.now();
for (var entry in _timerControllers.entries) {
if (entry.value.isRunning) {
_lastTimes[entry.key] = DateTime.now();
entry.value.timer?.cancel();
}
}
}
void _handleScreenUnlock() {
if (_lastPauseTime == null) return;
final now = DateTime.now();
for (var entry in _timerControllers.entries) {
if (entry.value.isRunning && _lastTimes.containsKey(entry.key)) {
final lastTime = _lastTimes[entry.key];
final diff = now.difference(lastTime).inSeconds;
entry.value.timer?.cancel();
entry.value.elapsedSeconds += diff;
if (entry.value.isRunning) {
entry.value.startTimer();
}
_lastTimes.remove(entry.key);
}
}
}
AppLifecycleState
to detect when the app goes to background/foregroundSharedPreferences
Timer.periodic
that maintains better precision?Any help or guidance would be greatly appreciated!
Upvotes: 0
Views: 22