Anderson André
Anderson André

Reputation: 21

Flutter: Multiple timers losing precision after screen lock/unlock

Problem

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.

Current Code

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);
    }
  }
}

enter image description here

What I've Tried

Questions

  1. Why are the timers losing precision during screen lock/unlock cycles?
  2. Is there a more accurate way to maintain elapsed time during these transitions?
  3. Is there an alternative to Timer.periodic that maintains better precision?

Any help or guidance would be greatly appreciated!

Upvotes: 0

Views: 22

Answers (0)

Related Questions