I am currently working on a timer app in flutter and use Dart Isolate for the timer to work in background.
It really works well in ios Simulator(iPhone 12 Pro) and android devices.
In ios real devices, when the app is run in background, the timer completely pause, and resume when the app is brought back from background.
Is there a way to make the timer to run in background in iOS devices?
TimerTask class:
import 'dart:async';
import 'dart:isolate';
typedef IsolateEnvironmentEntryPoint(
SendPort sendPort, ReceivePort receivePort);
class TimerTask {
Isolate isolate;
ReceivePort receivePort;
SendPort sendPort;
TimerTask._(this.isolate, this.receivePort, this.sendPort);
static Future<TimerTask> spawn(
IsolateEnvironmentEntryPoint entryPoint) async {
var completer = Completer<TimerTask>();
var isolateReceivePort = ReceivePort();
var envReceivePort = ReceivePort();
Isolate isolate;
isolateReceivePort.listen((msg) {
if (msg is SendPort) {
completer.complete(TimerTask._(isolate, envReceivePort, msg));
} else {
envReceivePort.sendPort.send(msg);
}
});
var args = [entryPoint, isolateReceivePort.sendPort];
isolate = await Isolate.spawn(backgroundTimerTask, args);
return completer.future;
}
static void backgroundTimerTask(List args) {
IsolateEnvironmentEntryPoint entryPoint = args[0];
SendPort sendPort = args[1];
var receivePort = ReceivePort();
sendPort.send(receivePort.sendPort);
entryPoint(sendPort, receivePort);
}
void stopTimerBgTask() {
if (isolate != null) {
isolate?.kill(priority: Isolate.immediate);
isolate = null;
print('Killed');
}
}
}
In main thread,
timer = await TimerTask.spawn(runBackgroundTimerTask);
timer.receivePort.asBroadcastStream().listen((msg) {
if (msg[0] == 'TimerRunInProgress') {
// setState to UI
} else if (msg[0] == 'TimerPaused') {
// setState to UI
} else if (msg[0] == 'ResumeTimer') {
// setState to UI
} else if (msg[0] == 'ResetTimer') {
// setState to UI
}
});
}
And the top-level isolate method runBackgroundTimerTask() is like this:
runBackgroundTimerTask(SendPort sendPort, ReceivePort receivePort) {
receivePort.listen((msg) {
if (msg[0] == 'TimerStarted') {
_tickerSubscription?.cancel();
_tickerSubscription = _ticker.tick(ticks: msg[1]).listen((timer) {
sendPort.send(['TimerRunInProgress', timer]);
});
} else if (msg[0] == 'PauseTimer') {
_tickerSubscription?.pause();
sendPort.send(['TimerPaused']);
} else if (msg[0] == 'ResumeTimer') {
_tickerSubscription?.resume();
sendPort.send(['ResumeTimer']);
} else if (msg[0] == 'ResetTimer') {
_tickerSubscription?.cancel();
sendPort.send(['ResetTimer']);
}
});
}
My Runner Info.plist:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.example.myapp</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>fetch</string>
<string>processing</string>
<string>remote-notification</string>
</array>