Open
Description
Required Reading
- Confirmed
Plugin Version
background_fetch: ^1.3.7
Flutter Doctor
➜ flutter_demo_app flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.24.5, on macOS 14.7.1 23H222 darwin-arm64, locale en-US)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.3)
[✓] VS Code (version 1.97.1)
[✓] Connected device (4 available)
[✓] Network resources
• No issues found!
Mobile operating-system(s)
- iOS
- Android
Device Manufacturer(s) and Model(s)
Iphone 8
Device operating-systems(s)
iOS 16.7.10
What happened?
Background Fetch Not Working in iOS Background Mode
- Implemented background_fetch in my Flutter app following the official documentation.
- Background fetch does not trigger when the app is in the background or terminated (kill state).
- The same implementation works perfectly on Android.
- Issue is observed on iOS (tested on physical devices).
Steps to Reproduce:
- Implement background_fetch as per the iOS installation guide.
- Enable Background Fetch in Xcode (Signing & Capabilities > Background Modes).
- Add BGTaskSchedulerPermittedIdentifiers to Info.plist.
- Run the app on a physical iOS device (not simulator).
- Put the app in the background or terminate it (kill state).
- Expected: Background fetch should trigger at intervals.
- Actual: Background fetch never fires on iOS when the app is backgrounded or killed.
Expected Behavior:
Background fetch should trigger at scheduled intervals even when the app is in the background or terminated (kill state).
Actual Behavior:
Background fetch never triggers when the app is in the background.
Environment:
- Flutter Version: 3.24.5
- iOS Version: iOS 16.7.10
- Device: iPhone 8
- background_fetch Version: 1.3.7
Plugin Code and/or Config
import 'dart:math';
import 'dart:developer' as log;
import 'package:background_fetch/background_fetch.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
void main() {
WidgetsFlutterBinding.ensureInitialized();
initLocalNotifications();
initBackgroundFetch();
runApp(const MyApp());
}
void initLocalNotifications() {
//set IOS Settings
const DarwinInitializationSettings initializationSettingsIos = DarwinInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
requestSoundPermission: false,
);
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
InitializationSettings initializationSettings = const InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIos,
);
flutterLocalNotificationsPlugin.initialize(initializationSettings);
}
Future<int> showNotification() async {
int notificationId = Random().nextInt(100000); // Generate a random notification ID
const AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails(
'background_fetch',
'Background Fetch',
channelDescription: 'Shows when background fetch is in progress',
importance: Importance.high,
priority: Priority.high,
ongoing: true,
autoCancel: false,
);
const NotificationDetails platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics,
);
await flutterLocalNotificationsPlugin.show(
notificationId,
'Fetching Data',
'Background fetch in progress',
platformChannelSpecifics,
);
log.log('Showing notification with ID: $notificationId');
return notificationId;
}
void removeNotification(int notificationId) async {
log.log('Removing notification with ID: $notificationId');
await flutterLocalNotificationsPlugin.cancel(notificationId);
}
void initBackgroundFetch() async {
BackgroundFetch.configure(
BackgroundFetchConfig(
minimumFetchInterval: 15, // 15 minutes
stopOnTerminate: false, // Continue even after app is closed
enableHeadless: true, // Allow background tasks
),
(String taskId) async {
print("[BackgroundFetch] Task received: $taskId");
int notificationId = await showNotification();
await checkInternetAndCallApi();
removeNotification(notificationId);
BackgroundFetch.finish(taskId);
},
(String taskId) async {
print("[BackgroundFetch] Task Timeout: $taskId");
BackgroundFetch.finish(taskId);
},
);
BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
}
@pragma('vm:entry-point')
void backgroundFetchHeadlessTask(HeadlessTask task) async {
print("[BackgroundFetch] Headless event received: ${task.taskId}");
int notificationId = await showNotification();
await checkInternetAndCallApi();
removeNotification(notificationId);
BackgroundFetch.finish(task.taskId);
}
Future<void> checkInternetAndCallApi() async {
var connectivityResult = await Connectivity().checkConnectivity();
// check if there is an internet connection
if (connectivityResult != ConnectivityResult.none) {
try {
var response = await http.post(Uri.parse('https://664c95cb35bbda10988101cb.mockapi.io/Yesss'));
// check staTus code 200 TO 299
if (response.statusCode == 200 || response.statusCode == 201) {
print('API Call Success: ${response.body}');
} else {
print('API Call Failed ${response.statusCode}');
}
} catch (e) {
print('API Call Error: $e');
}
} else {
print('No Internet Connection');
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Background API Call Example')),
body: const Center(
child: Text('Running...'),
), // Placeholder
),
);
}
}
Relevant log output
Did not get any logs or errors