import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; void main() { // Initialize port for communication between TaskHandler and UI. FlutterForegroundTask.initCommunicationPort(); runApp(const ExampleApp()); } // The callback function should always be a top-level or static function. @pragma('vm:entry-point') void startCallback() { FlutterForegroundTask.setTaskHandler(MyTaskHandler()); } class MyTaskHandler extends TaskHandler { static const String incrementCountCommand = 'incrementCount'; int _count = 0; void _incrementCount() { _count++; // Update notification content. FlutterForegroundTask.updateService( notificationTitle: 'Hello MyTaskHandler :)', notificationText: 'count: $_count', ); // Send data to main isolate. FlutterForegroundTask.sendDataToMain(_count); } // Called when the task is started. @override Future onStart(DateTime timestamp, TaskStarter starter) async { print('onStart(starter: ${starter.name})'); _incrementCount(); } // Called based on the eventAction set in ForegroundTaskOptions. @override void onRepeatEvent(DateTime timestamp) { _incrementCount(); } // Called when the task is destroyed. @override Future onDestroy(DateTime timestamp, bool isTimeout) async { print('onDestroy(isTimeout: $isTimeout)'); } // Called when data is sent using `FlutterForegroundTask.sendDataToTask`. @override void onReceiveData(Object data) { print('onReceiveData: $data'); if (data == incrementCountCommand) { _incrementCount(); } } // Called when the notification button is pressed. @override void onNotificationButtonPressed(String id) { print('onNotificationButtonPressed: $id'); } // Called when the notification itself is pressed. @override void onNotificationPressed() { print('onNotificationPressed'); } // Called when the notification itself is dismissed. @override void onNotificationDismissed() { print('onNotificationDismissed'); } } class ExampleApp extends StatelessWidget { const ExampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( routes: { '/': (context) => const ExamplePage(), '/second': (context) => const SecondPage(), }, initialRoute: '/', ); } } class ExamplePage extends StatefulWidget { const ExamplePage({super.key}); @override State createState() => _ExamplePageState(); } class _ExamplePageState extends State { final ValueNotifier _taskDataListenable = ValueNotifier(null); Future _requestPermissions() async { // Android 13+, you need to allow notification permission to display foreground service notification. // // iOS: If you need notification, ask for permission. final NotificationPermission notificationPermission = await FlutterForegroundTask.checkNotificationPermission(); if (notificationPermission != NotificationPermission.granted) { await FlutterForegroundTask.requestNotificationPermission(); } if (Platform.isAndroid) { // Android 12+, there are restrictions on starting a foreground service. // // To restart the service on device reboot or unexpected problem, you need to allow below permission. if (!await FlutterForegroundTask.isIgnoringBatteryOptimizations) { // This function requires `android.permission.REQUEST_IGNORE_BSCTERY_OPTIMIZATIONS` permission. await FlutterForegroundTask.requestIgnoreBatteryOptimization(); } // Use this utility only if you provide services that require long-term survival, // such as exact alarm service, healthcare service, or Bluetooth communication. // // This utility requires the "android.permission.SCHEDULE_EXACT_ALARM" permission. // Using this permission may make app distribution difficult due to Google policy. if (!await FlutterForegroundTask.canScheduleExactAlarms) { // When you call this function, will be gone to the settings page. // So you need to explain to the user why set it. await FlutterForegroundTask.openAlarmsAndRemindersSettings(); } } } void _initService() { FlutterForegroundTask.init( androidNotificationOptions: AndroidNotificationOptions( channelId: 'foreground_service', channelName: 'Foreground Service Notification', channelDescription: 'This notification appears when the foreground service is running.', onlyAlertOnce: true, ), iosNotificationOptions: const IOSNotificationOptions( showNotification: false, playSound: false, ), foregroundTaskOptions: ForegroundTaskOptions( eventAction: ForegroundTaskEventAction.repeat(5000), autoRunOnBoot: true, autoRunOnMyPackageReplaced: true, allowWakeLock: true, allowWifiLock: true, ), ); } Future _startService() async { if (await FlutterForegroundTask.isRunningService) { return FlutterForegroundTask.restartService(); } else { return FlutterForegroundTask.startService( // You can manually specify the foregroundServiceType for the service // to be started, as shown in the comment below. // serviceTypes: [ // ForegroundServiceTypes.dataSync, // ForegroundServiceTypes.remoteMessaging, // ], serviceId: 256, notificationTitle: 'Foreground Service is running', notificationText: 'Tap to return to the app', notificationIcon: null, notificationButtons: [ const NotificationButton(id: 'btn_hello', text: 'hello'), ], notificationInitialRoute: '/second', callback: startCallback, ); } } Future _stopService() { return FlutterForegroundTask.stopService(); } void _onReceiveTaskData(Object data) { print('onReceiveTaskData: $data'); _taskDataListenable.value = data; } void _incrementCount() { FlutterForegroundTask.sendDataToTask(MyTaskHandler.incrementCountCommand); } @override void initState() { super.initState(); // Add a callback to receive data sent from the TaskHandler. FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData); WidgetsBinding.instance.addPostFrameCallback((_) { // Request permissions and initialize the service. _requestPermissions(); _initService(); }); } @override void dispose() { // Remove a callback to receive data sent from the TaskHandler. FlutterForegroundTask.removeTaskDataCallback(_onReceiveTaskData); _taskDataListenable.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // ** optional ** // A widget that minimize the app without closing it when the user presses // the soft back button. It only works when the service is running. // // This widget must be declared above the [Scaffold] widget. return WithForegroundTask( child: Scaffold( appBar: AppBar( title: const Text('Flutter Foreground Task'), centerTitle: true, ), body: SafeArea( child: Column( children: [ Expanded(child: _buildCommunicationDataText()), _buildServiceControlButtons(), ], ), ), ), ); } Widget _buildCommunicationDataText() { return ValueListenableBuilder( valueListenable: _taskDataListenable, builder: (context, data, _) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('You received data from TaskHandler:'), Text('$data', style: Theme.of(context).textTheme.headlineMedium), ], ), ); }, ); } Widget _buildServiceControlButtons() { buttonBuilder(String text, {VoidCallback? onPressed}) { return ElevatedButton( onPressed: onPressed, child: Text(text), ); } return Padding( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ buttonBuilder('start service', onPressed: _startService), buttonBuilder('stop service', onPressed: _stopService), buttonBuilder('increment count', onPressed: _incrementCount), ], ), ); } } class SecondPage extends StatelessWidget { const SecondPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Second Page'), centerTitle: true, ), body: Center( child: ElevatedButton( onPressed: Navigator.of(context).pop, child: const Text('pop this page'), ), ), ); } }