1

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)
    }
}

4
  • You will need "always" location permission, not "when in use". You can then get a stream of location updates. You cannot and do not need to use any timers on ios. I don't know whether this plug-in is written to take advantage of iOS background location updates
    – Paulw11
    Commented Jan 22 at 8:31
  • Yes, giving location permission 'always', but it's not working when the app is closed.
    – Rakibul
    Commented Jan 23 at 5:40
  • You need something that is listening to the stream of location updates that are delivered to the native location manager. You can't rely on periodically fetching the location.
    – Paulw11
    Commented Jan 23 at 7:39
  • Yes, I have implemented it now. iosConfiguration: IosConfiguration( autoStart: false, onForeground: onStart, onBackground: onBackground, ), inside onBackground implemented with positionStream. Still not working when the app is closed. One more thing: when I close the app in Android, the console is not closing. It's showing everything happens in onCreate. But for iOS, it's terminating everything.
    – Rakibul
    Commented Jan 23 at 10:20

1 Answer 1

1

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.

1
  • Yes, I have read that. I missed this point on the question. If it will awaken after every 15 minutes, then I have to implement the onBackground or it will work with current settings.
    – Rakibul
    Commented Jan 23 at 10:20

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.