Flutter/Dart: can you run a background service with Future.delayed?

821

In reviewing your example, you use Timer() initially, and then _runWorkersLoop() implements it's own "periodic" loop by calling itself with Future.delayed(). One way to maybe simplify this is to use Timer.periodic() which you call once and until you cancel it, it will repeat.

https://api.dart.dev/stable/2.12.0/dart-async/Timer/Timer.periodic.html

Then you have a timer instance that you can check if it's running with isRunning() and you can cancel at any time with cancel().

I looked at the source for cron lib and it uses Future.microtask which is similar to Future.delayed. Generally using a lib like that will help give you:

  • more eyes on the code to fix any bugs versus a home grown solution
  • more general functionality that you might want to use later
  • easier to learn/understand for someone picking up your code through more examples of use available

I assume you don't have any critical timing requirements down to the millisecond with when your stuff runs, so I think you might be interested in looking at a periodic timer as mentioned above.

One thing you might want to protect is that if a bug later calls your _runWorkersLoop() function when it's already running, it will call Future.delayed() again even though one already is waiting. You don't have a way to check for existing Future.delayed() instances but with Timer.peridic() you can use the "isRunning()" to check.

Share:
821
mrj
Author by

mrj

Updated on December 28, 2022

Comments

  • mrj
    mrj over 1 year

    I have to run a bunch of tasks every day on a Dart/Flutter project. This is how I currently do it:

    class TaskScheduler {
      DateTime _lastUpdate;
      bool _isRunning = false;
    
      void launchDailyTasks() async {
        //make sure tasks are not already scheduled
        if (_isRunning) return;
    
        //check last updates
        if (_lastUpdate == null) {
          SharedPreferences prefs = await SharedPreferences.getInstance();
          final _stamp = prefs.getInt(prefsKey(AppConstants.LAST_SYNC));
          if (_stamp != null) {
            _lastUpdate = DateTime.fromMillisecondsSinceEpoch(_stamp);
          } else {
            _lastUpdate = DateTime.now();
          }
        }
    
        if (_lastUpdate.isBefore(DateTime.now().add(Duration(days: 1)))) {
          _runWorkersLoop();
        } else {
          final _delay =
              DateTime.now().difference(_lastUpdate.add(Duration(days: 1)));
          Timer(_delay, () => _runWorkersLoop());
        }
      }
    
      void _runWorkersLoop() async {
        _isRunning = true;
        _startDailyTasks();
        SharedPreferences prefs = await SharedPreferences.getInstance();
        prefs.setInt(prefsKey(AppConstants.LAST_SYNC),
            DateTime.now().millisecondsSinceEpoch);
        _lastUpdate = DateTime.now();
        Future.delayed(Duration(days: 1), () => _runWorkersLoop());
      }
    
    }
    

    And so I've been wondering: is this wrong? Why should I use a package like https://pub.dev/packages/cron to do this if this works?

    • Smashing
      Smashing about 3 years
      Are you sure this works? What happens if, for example, android decides to kill your process for some reason? This will stop working then.
    • mrj
      mrj about 3 years
      @Smashing true but tbh I don't care since I don't need data from the background. Those tasks have to be run only when the app is alive and are always launched at app start.
  • mrj
    mrj about 3 years
    Thanks for pointing out periodic, I'll definitely change that. Otherwise no risk of crashing the app in the background or memory leak? I thought my approach was way too simple to work properly...
  • Eradicatore
    Eradicatore about 3 years
    I don't believe there is any risk for memory leaks or crashes, from this anyway. :-) If you haven't watched the flutter videos on async I would highly recommend them. youtube.com/watch?v=OTS-ap9_aXc