Skip to content

[Bug]: Background Fetch Not Working in iOS Background Mode #388

Open
@mahammad-webelight

Description

@mahammad-webelight

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:

  1. Implement background_fetch as per the iOS installation guide.
  2. Enable Background Fetch in Xcode (Signing & Capabilities > Background Modes).
  3. Add BGTaskSchedulerPermittedIdentifiers to Info.plist.
  4. Run the app on a physical iOS device (not simulator).
  5. Put the app in the background or terminate it (kill state).
  6. Expected: Background fetch should trigger at intervals.
  7. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingstale

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions