To keep the timer running while the app is closed, you need to use a service. Listen to the broadcast of the service in the activity. See this SO answer to learn whether registering the receiver in onCreate, onStart, or onResume is right for you.
Please note: This timer only runs when the app is running - it does not schedule anything to be run in the background using OS-specific scheduling mechanisms, nor does it attempt to wake the device or app.
Answering the question of how to implement your specific timer case doesn't actually have to do with background code. Overall running code in the background is something discouraged on mobile operating systems.
For example, iOS Documentation discusses background code in greater detail here: https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
Instead mobile operating systems provide apis (like a timer/alarm/notification apis) to call back to your application after a specific time. For example on iOS you can request that your application be notified/woken at a specific point in the future via UINotificationRequest: https://developer.apple.com/reference/usernotifications/unnotificationrequest This allows them to kill/suspend your app to achieve better power savings and instead have a single highly-efficent shared system service for tracking these notifications/alarms/geofencing, etc.
Flutter does not currently provide any wrappers around these OS services out-of-the-box, however it is straighforward to write your own using our platform-services model: flutter.io/platform-services
We're working on a system for publishing/sharing service integrations like this so that once one person writes this integration (for say scheduling some future execution of your app) everyone can benefit.
Separately, the more general question of "is it possible to run background Dart code" (without having a FlutterView active on screen), is "not yet". We have a bug on file: https://github.com/flutter/flutter/issues/3671 and an open issue: https://github.com/flutter/flutter/issues/32164
The use-case driving that kind of back-ground code execution is when your app receives a notification, wants to process it using some Dart code without bringing your app to the front. If you have other use cases for background code you'd like us to know about, comments are most welcome on that bug!
Short answer: no, it's not possible, although I have observed a different behavior for the display going to sleep. The following code will help you understand the different states of a Flutter app on Android, tested with the these Flutter and Flutter Engine versions:
Create a new Flutter app, and replace the content of lib/main.dart
with this code:
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class LifecycleWatcher extends StatefulWidget {
@override
_LifecycleWatcherState createState() => new _LifecycleWatcherState();
}
class _LifecycleWatcherState extends State<LifecycleWatcher>
with WidgetsBindingObserver {
AppLifecycleState _lastLifecyleState;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void onDeactivate() {
super.deactivate();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
print("LifecycleWatcherState#didChangeAppLifecycleState state=${state.toString()}");
setState(() {
_lastLifecyleState = state;
});
}
@override
Widget build(BuildContext context) {
if (_lastLifecyleState == null)
return new Text('This widget has not observed any lifecycle changes.');
return new Text(
'The most recent lifecycle state this widget observed was: $_lastLifecyleState.');
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter App Lifecycle'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _timerCounter = 0;
// ignore: unused_field only created once
Timer _timer;
_MyHomePageState() {
print("_MyHomePageState#constructor, creating new Timer.periodic");
_timer = new Timer.periodic(
new Duration(milliseconds: 3000), _incrementTimerCounter);
}
void _incrementTimerCounter(Timer t) {
print("_timerCounter is $_timerCounter");
setState(() {
_timerCounter++;
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(config.title),
),
body: new Block(
children: [
new Text(
'Timer called $_timerCounter time${ _timerCounter == 1 ? '' : 's' }.',
),
new LifecycleWatcher(),
],
),
);
}
}
When launching the app, the value of _timerCounter is incremented every 3s. A text field below the counter will show any AppLifecycleState changes for the Flutter app, you will see corresponding output in the Flutter debug log, e.g.:
[raju@eagle:~/flutter/helloworld]$ flutter run
Launching lib/main.dart on SM N920S in debug mode...
Building APK in debug mode (android-arm)... 6440ms
Installing build/app.apk... 6496ms
I/flutter (28196): _MyHomePageState#constructor, creating new Timer.periodic
Syncing files to device...
I/flutter (28196): _timerCounter is 0
🔥 To hot reload your app on the fly, press "r" or F5. To restart the app entirely, press "R".
The Observatory debugger and profiler is available at: http://127.0.0.1:8108/
For a more detailed help message, press "h" or F1. To quit, press "q", F10, or Ctrl-C.
I/flutter (28196): _timerCounter is 1
I/flutter (28196): LifecycleWatcherState#didChangeAppLifecycleState state=AppLifecycleState.paused
I/flutter (28196): _timerCounter is 2
I/flutter (28196): _timerCounter is 3
I/flutter (28196): LifecycleWatcherState#didChangeAppLifecycleState state=AppLifecycleState.resumed
I/flutter (28196): _timerCounter is 4
I/flutter (28196): LifecycleWatcherState#didChangeAppLifecycleState state=AppLifecycleState.paused
I/flutter (28196): _timerCounter is 5
I/flutter (28196): _timerCounter is 6
I/flutter (28196): _timerCounter is 7
I/flutter (28196): LifecycleWatcherState#didChangeAppLifecycleState state=AppLifecycleState.resumed
I/flutter (28196): LifecycleWatcherState#didChangeAppLifecycleState state=AppLifecycleState.paused
I/flutter (28196): _timerCounter is 8
I/flutter (28196): _MyHomePageState#constructor, creating new Timer.periodic
I/flutter (28196): _timerCounter is 0
I/flutter (28196): _timerCounter is 1
For the above log output, here are the steps I did:
flutter run
Switching between apps, pressing power or back button
When switching to another app, or when pressing the power button to turn of the screen the timer continues to run. But when pressing the back button while the Flutter app has the focus, the Activity gets destroyed, and with it the Dart isolate. You can test that by connecting to the Dart Observatory when switching between apps, or turning of the screen. The Observatory will show an active Flutter app Isolate running. But when pressing the back button, the Observatory shows no running Isolate. The behavior was confirmed on a Galaxy Note 5 running Android 6.x, and a Nexus 4 running Android 4.4.x.
Flutter app lifecycle and Android lifecycle For the Flutter widget layer, only the paused and resumed states are exposed. Destroy is handled by Android Activity for an Android Flutter app:
/**
* @see android.app.Activity#onDestroy()
*/
@Override
protected void onDestroy() {
if (flutterView != null) {
flutterView.destroy();
}
super.onDestroy();
}
Since the Dart VM for a Flutter app is running inside the Activity, the VM will be stopped every time the Activity gets destroyed.
Flutter Engine code logic
This doesn't directly answer your question, but will give you some more detailed background info on how the Flutter engine handles state changes for Android.
Looking through the Flutter engine code it becomes obvious that the animation loop is paused when the FlutterActivity receives the Android Activity#onPause event. When the application goes into paused state, according to the source comment here the following happens:
"The application is not currently visible to the user. When the application is in this state, the engine will not call the [onBeginFrame] callback."
Based on my testing the timer continues to work even with the UI rendering being paused, which makes sense. It would be good to send an event into the widget layer using the WidgetsBindingObserver when the Activity gets destroyed, so developers can make sure to store the state of the Flutter app until the Activity is resumed.
I have faced the same problem and my solution to this specific case (countdown timer) was to use same logic as used in some native android/ios Apps out there, which is:
Duration remainingTime = _endingTime.difference(dateTimeNow);
NOTE: Ending datetime value has been stored in a singleton, I didn't use SharedPreferences as no need in my case but it's an acceptable option in case you needed it.
in details:
I have created this handler to set and get remaining time:
class TimerHandler {
DateTime _endingTime;
TimerHandler._privateConstructor();
TimerHandler();
static final TimerHandler _instance = new TimerHandler();
static TimerHandler get instance => _instance;
int get remainingSeconds {
final DateTime dateTimeNow = new DateTime.now();
Duration remainingTime = _endingTime.difference(dateTimeNow);
// Return in seconds
return remainingTime.inSeconds;
}
void setEndingTime(int durationToEnd) {
final DateTime dateTimeNow = new DateTime.now();
// Ending time is the current time plus the remaining duration.
this._endingTime = dateTimeNow.add(
Duration(
seconds: durationToEnd,
),
);
}
}
final timerHandler = TimerHandler.instance;
then inside timer screen, I watched the app's life cycle;
NOTES:
1- I don't check the timer state before set new remaining duration, because the logic that I need in my App is to push the endingTime in case the user paused the timer, instead of reduce the timerDuration, totally up to the use case.
2- My timer lives in a bloc( TimerBloc).
class _TimerScreenState extends State<TimerScreen> {
int remainingDuration;
//...
@override
void initState() {
super.initState();
SystemChannels.lifecycle.setMessageHandler((msg) {
if (msg == AppLifecycleState.paused.toString() ) {
// On AppLifecycleState: paused
remainingDuration = BlocProvider.of<TimerBloc>(context).currentState.duration ?? 0;
timerHandler.setEndingTime(remainingDuration);
setState((){});
}
if (msg == AppLifecycleState.resumed.toString() ) {
// On AppLifecycleState: resumed
BlocProvider.of<TimerBloc>(context).dispatch(
Start(
duration: timerHandler.remainingSeconds,
),
);
setState((){});
}
return;
});
}
//....
}
in case something's not clear just leave a comment.
You could use the flutter_workmanager plugin.
It's better than the above mentioned AlarmManager
since this is not recommended any more for Android.
The plugin also always for iOS
background execution
This plugin allows you do register some background work and get a callback in Dart when it happened so you can perform a custom action.
void callbackDispatcher() {
Workmanager.executeTask((backgroundTask) {
switch(backgroundTask) {
case Workmanager.iOSBackgroundTask:
case "firebaseTask":
print("You are now in a background Isolate");
print("Do some work with Firebase");
Firebase.doSomethingHere();
break;
}
return Future.value(true);
});
}
void main() {
Workmanager.initialize(callbackDispatcher);
Workmanager.registerPeriodicTask(
"1",
"firebaseTask",
frequency: Duration(days: 1),
constraints: WorkManagerConstraintConfig(networkType: NetworkType.connected),
);
runApp(MyApp());
}
You can use the android_alarm_manager flutter plugin which lets you run Dart code in the background when an alarm fires.
Another way with more control would be to write a native Android service (using Java or Kotlin) for your app that communicates with the flutter frontend via device storage or shared prefs.
i think firstly you neeed to way preventing system from killing the FlutterActivity when you click back button
you can acheive that by calling native android code from flutter there are function called moveToBack(true) allows you to keep FlutterActivity running.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With