Rakibul
Rakibul

Reputation: 1

iOS Background Location Updates Not Working When App is Closed Using Flutter

The provided code snippet is part of a Flutter application that uses the flutter_background_service package to run background tasks. The onStart function is the entry point for the background service, which is responsible for periodically collecting and updating the device's location. This is working perfect on android, even when I close the app. But in IOS it's working only when app is running or paused.

Code:

Future<void> initializeBackgroundService() async {
  final service = FlutterBackgroundService();

  await service.configure(
    androidConfiguration: AndroidConfiguration(
      onStart: onStart,
      autoStart: false,
      autoStartOnBoot: false,
      isForegroundMode: true,
      initialNotificationTitle: "Tracking",
      initialNotificationContent: 'Refreshing',
    ),
    iosConfiguration: IosConfiguration(
      autoStart: false,
      onForeground: onStart,
    ),
  );
}

Timer? timer;

@pragma('vm:entry-point')
void onStart(ServiceInstance service) async {
  print("onStart called with $service");
  try {
    DartPluginRegistrant.ensureInitialized();
    String __ = "";
    String version = "";
    String __ = "";
    String url = "";
    Position? position;

    if (Platform.isAndroid) {
      log("device is android");
      if (service is AndroidServiceInstance) {
        log("device is android service instance");
        service.on('setAsForeground').listen((event) async {
          //my codes
        });
      }
    } else if (Platform.isIOS) {
      service.on('setAsForeground').listen((event) async {
        //my codes
      });
    }

    if (Platform.isIOS) {
      final NotificationService notificationService = NotificationService();
      await notificationService.initializePlatformNotifications();
    }

    service.on('stopService').listen((event) async {
      timer?.cancel();
      await service.stopSelf();
      log("Service stopped");
    });

    if (Platform.isAndroid) {
      log("first notification android");
      if (service is AndroidServiceInstance && await service.isForegroundService()) {
        service.setForegroundNotificationInfo(
          title: "Tracking",
          content: "Last updated at ${DateFormat.jm().format(DateTime.now())}, ${DateFormat.yMd().format(DateTime.now())}",
        );
      }
    } else if (Platform.isIOS) {
      await NotificationService.showNotification(
        id: 0,
        title: "Tracking",
        body: "Last updated at ${DateFormat.jm().format(DateTime.now())}, ${DateFormat.yMd().format(DateTime.now())}",
        data: "",
      );
    }

    timer?.cancel();
    timer = Timer.periodic(const Duration(seconds: 10), (timer) async {
      if (Platform.isAndroid) {
        log("authcode for android $authcode");
        if (service is AndroidServiceInstance) {
          if (await service.isForegroundService()) {
            Position? position;
            bool isLocationEnabled = await Geolocator.isLocationServiceEnabled();
            bool isPermissionGranted = await Geolocator.checkPermission() ==
                LocationPermission.whileInUse ||
                await Geolocator.checkPermission() == LocationPermission.always;
            if (isLocationEnabled && isPermissionGranted) {
              position = await Geolocator.getCurrentPosition(
                  desiredAccuracy: LocationAccuracy.bestForNavigation);
              if (position.accuracy > 200) {
                StreamSubscription<Position> positionStream =
                Geolocator.getPositionStream(
                  locationSettings: AndroidSettings(
                    accuracy: LocationAccuracy.bestForNavigation,
                    distanceFilter: 0,
                    intervalDuration: const Duration(seconds: 1),
                  ),
                ).listen((Position value) {
                  position = value;
                });
                await Future.delayed(const Duration(seconds: 5));
                await positionStream.cancel();
              }
              log("authcode for android before call $authcode");
              log("url for android before call $url");
              //sending location from here
            } else {
              timer.cancel();
              service.stopSelf();
            }
          }
        }
      } else if (Platform.isIOS) {
        Position? position;
        bool isLocationEnabled = await Geolocator.isLocationServiceEnabled();
        bool isPermissionGranted = await Geolocator.checkPermission() ==
            LocationPermission.whileInUse ||
            await Geolocator.checkPermission() == LocationPermission.always;
        if (isLocationEnabled && isPermissionGranted) {
          position = await Geolocator.getCurrentPosition(
              desiredAccuracy: LocationAccuracy.bestForNavigation);
          if (position.accuracy > 200) {
            StreamSubscription<Position> positionStream =
            Geolocator.getPositionStream(
              locationSettings: AppleSettings(
                accuracy: LocationAccuracy.bestForNavigation,
                distanceFilter: 0,
                timeLimit: const Duration(seconds: 1),
                activityType: ActivityType.otherNavigation,
              ),
            ).listen((Position value) {
              position = value;
            });
            await Future.delayed(const Duration(seconds: 5));
            await positionStream.cancel();
          }
         //sending location from here
        
        } else {
          timer.cancel();
          service.stopSelf();
        }
      }
      print('FLUTTER BACKGROUND SERVICE: ${DateTime.now()}');
    });
  } catch (e, stack) {
    print(e);
    print('stack: $stack');
  }
}

I configured the flutter_background_service package and enabled UIBackgroundModes for location updates in the Info.plist file.

<key>BGTaskSchedulerPermittedIdentifiers</key>
    <array>
        <string>dev.flutter.background.refresh</string>
        <string>myBackgroundTask</string>
    </array>
<key>UIBackgroundModes</key>
    <array>
        <string>location</string>
        <string>fetch</string>
        <string>remote-notification</string>
    </array>

And this is Appdelegate.swift

import UIKit
import Flutter
import flutter_background_service_ios

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        SwiftFlutterBackgroundServicePlugin.taskIdentifier = "dev.flutter.background.refresh"
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

Upvotes: 0

Views: 42

Answers (1)

Randal Schwartz
Randal Schwartz

Reputation: 44046

The last paragraph of the API doc for this package looks like it perfectly describes the symptoms you're seeing:

Service terminated when app is in background (minimized) on iOS

Keep in your mind, iOS doesn't have a long running service feature like Android. So, it's not possible to keep your application running when it's in background because the OS will suspend your application soon. Currently, this plugin provide onBackground method, that will be executed periodically by Background Fetch capability provided by iOS. It cannot be faster than 15 minutes and only alive about 15-30 seconds.

Upvotes: 1

Related Questions