I’m building a Flutter app which will use EventChannel and MethodChannel that will communicate between Native and Flutter code. I’m using the IOS Region Monitoring to detect if the user have exited or entered the region.
On the Flutter side, I will check for all necessary permissions before passing the regions to the Native side through Method Channel for region monitoring and uses EventChannel to inform the Flutter side that the user have either exited or entered the region. Everything works as expected when the app is in alive in foreground or in the background. However, when I terminate the app, the region monitoring doesn’t seem to work anymore.
I tried this prototype which is fully native, it works perfectly on foreground, background and terminated state.
Below is my code on the Native side
@objc class AppDelegate: FlutterAppDelegate {
var locationManager: CLLocationManager!
var eventSink: FlutterEventSink?
var regions: [CLCircularRegion] = []
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
let controller = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "location", binaryMessenger: controller.binaryMessenger)
let eventChannel = FlutterEventChannel(name: "location_stream", binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler(handle)
eventChannel.setStreamHandler(self)
locationManager = CLLocationManager()
locationManager.delegate = self
GeneratedPluginRegistrant.register(with: self)
if launchOptions?[UIApplication.LaunchOptionsKey.location] != nil {
NSLog("regrion monitoring: launchoptionkey")
if locationManager == nil {
locationManager = CLLocationManager()
locationManager.delegate = self
eventSink?(["type": 2, "identifier": "woke up"])
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
print("regrion monitoring: here")
result(FlutterMethodNotImplemented)
private func startMonitoring() {
print("regrion monitoring: start")
eventSink?(["type": 3, "identifier": "startMonitoring"])
eventSink?(["type": 3, "identifier": "startMonitoring: (o.identifier)"])
print("regrion monitoring: (o.identifier)")
locationManager.startMonitoring(for: o)
private func stopMonitoring() {
print("regrion monitoring: stop")
locationManager.stopMonitoring(for: o)
// locationManager.delegate = nil
private func setRegions(call: FlutterMethodCall) {
let argResult = call.arguments as? [String: [String: Any]]
for s in argResult!.keys {
print("regrion monitoring: (s)");
regions.append(CLCircularRegion(
center: CLLocationCoordinate2D(latitude: argResult![s]!["latitude"] as! Double, longitude: argResult![s]!["longitude"] as! Double),
radius: argResult![s]!["radius"] as! Double,
identifier: argResult![s]!["description"] as! String))
private func handleEvent(forRegion region: CLRegion, type: Int) {
print("regrion monitoring: (region.identifier)")
eventSink?(["type": type, "identifier": region.identifier])
print("regrion monitoring: after event sink")
extension AppDelegate: CLLocationManagerDelegate {
public func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
print("regrion monitoring: didStartMonitoring")
print("(region.identifier)")
public func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("regrion monitoring: Enter")
if region is CLCircularRegion {
handleEvent(forRegion: region, type: 0)
public func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
print("regrion monitoring: Exit")
if region is CLCircularRegion {
handleEvent(forRegion: region, type: 1)
extension AppDelegate: FlutterStreamHandler {
@objc public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
@objc public func onCancel(withArguments arguments: Any?) -> FlutterError? {
<code>import UIKit
import CoreLocation
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
var locationManager: CLLocationManager!
var eventSink: FlutterEventSink?
var regions: [CLCircularRegion] = []
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "location", binaryMessenger: controller.binaryMessenger)
let eventChannel = FlutterEventChannel(name: "location_stream", binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler(handle)
eventChannel.setStreamHandler(self)
locationManager = CLLocationManager()
locationManager.delegate = self
GeneratedPluginRegistrant.register(with: self)
if launchOptions?[UIApplication.LaunchOptionsKey.location] != nil {
NSLog("regrion monitoring: launchoptionkey")
if locationManager == nil {
locationManager = CLLocationManager()
locationManager.delegate = self
startMonitoring()
}
eventSink?(["type": 2, "identifier": "woke up"])
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
print("regrion monitoring: here")
switch call.method {
case "startMonitoring":
startMonitoring()
result(nil)
case "stopMonitoring":
stopMonitoring()
result(nil)
case "setRegions":
setRegions(call: call)
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
private func startMonitoring() {
print("regrion monitoring: start")
eventSink?(["type": 3, "identifier": "startMonitoring"])
for o in regions {
eventSink?(["type": 3, "identifier": "startMonitoring: (o.identifier)"])
print("regrion monitoring: (o.identifier)")
o.notifyOnExit = true;
o.notifyOnEntry = true;
locationManager.startMonitoring(for: o)
}
}
private func stopMonitoring() {
print("regrion monitoring: stop")
for o in regions {
locationManager.stopMonitoring(for: o)
}
// locationManager.delegate = nil
eventSink = nil
}
private func setRegions(call: FlutterMethodCall) {
let argResult = call.arguments as? [String: [String: Any]]
for s in argResult!.keys {
print("regrion monitoring: (s)");
regions.append(CLCircularRegion(
center: CLLocationCoordinate2D(latitude: argResult![s]!["latitude"] as! Double, longitude: argResult![s]!["longitude"] as! Double),
radius: argResult![s]!["radius"] as! Double,
identifier: argResult![s]!["description"] as! String))
}
}
private func handleEvent(forRegion region: CLRegion, type: Int) {
print("regrion monitoring: (region.identifier)")
eventSink?(["type": type, "identifier": region.identifier])
print("regrion monitoring: after event sink")
}
}
extension AppDelegate: CLLocationManagerDelegate {
public func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
print("regrion monitoring: didStartMonitoring")
print("(region.identifier)")
}
public func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("regrion monitoring: Enter")
if region is CLCircularRegion {
handleEvent(forRegion: region, type: 0)
}
}
public func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
print("regrion monitoring: Exit")
if region is CLCircularRegion {
handleEvent(forRegion: region, type: 1)
}
}
}
extension AppDelegate: FlutterStreamHandler {
@objc public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
print("onListen")
self.eventSink = events
return nil
}
@objc public func onCancel(withArguments arguments: Any?) -> FlutterError? {
print("onCancel")
self.eventSink = nil
return nil
}
}
</code>
import UIKit
import CoreLocation
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
var locationManager: CLLocationManager!
var eventSink: FlutterEventSink?
var regions: [CLCircularRegion] = []
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "location", binaryMessenger: controller.binaryMessenger)
let eventChannel = FlutterEventChannel(name: "location_stream", binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler(handle)
eventChannel.setStreamHandler(self)
locationManager = CLLocationManager()
locationManager.delegate = self
GeneratedPluginRegistrant.register(with: self)
if launchOptions?[UIApplication.LaunchOptionsKey.location] != nil {
NSLog("regrion monitoring: launchoptionkey")
if locationManager == nil {
locationManager = CLLocationManager()
locationManager.delegate = self
startMonitoring()
}
eventSink?(["type": 2, "identifier": "woke up"])
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
print("regrion monitoring: here")
switch call.method {
case "startMonitoring":
startMonitoring()
result(nil)
case "stopMonitoring":
stopMonitoring()
result(nil)
case "setRegions":
setRegions(call: call)
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
private func startMonitoring() {
print("regrion monitoring: start")
eventSink?(["type": 3, "identifier": "startMonitoring"])
for o in regions {
eventSink?(["type": 3, "identifier": "startMonitoring: (o.identifier)"])
print("regrion monitoring: (o.identifier)")
o.notifyOnExit = true;
o.notifyOnEntry = true;
locationManager.startMonitoring(for: o)
}
}
private func stopMonitoring() {
print("regrion monitoring: stop")
for o in regions {
locationManager.stopMonitoring(for: o)
}
// locationManager.delegate = nil
eventSink = nil
}
private func setRegions(call: FlutterMethodCall) {
let argResult = call.arguments as? [String: [String: Any]]
for s in argResult!.keys {
print("regrion monitoring: (s)");
regions.append(CLCircularRegion(
center: CLLocationCoordinate2D(latitude: argResult![s]!["latitude"] as! Double, longitude: argResult![s]!["longitude"] as! Double),
radius: argResult![s]!["radius"] as! Double,
identifier: argResult![s]!["description"] as! String))
}
}
private func handleEvent(forRegion region: CLRegion, type: Int) {
print("regrion monitoring: (region.identifier)")
eventSink?(["type": type, "identifier": region.identifier])
print("regrion monitoring: after event sink")
}
}
extension AppDelegate: CLLocationManagerDelegate {
public func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
print("regrion monitoring: didStartMonitoring")
print("(region.identifier)")
}
public func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("regrion monitoring: Enter")
if region is CLCircularRegion {
handleEvent(forRegion: region, type: 0)
}
}
public func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
print("regrion monitoring: Exit")
if region is CLCircularRegion {
handleEvent(forRegion: region, type: 1)
}
}
}
extension AppDelegate: FlutterStreamHandler {
@objc public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
print("onListen")
self.eventSink = events
return nil
}
@objc public func onCancel(withArguments arguments: Any?) -> FlutterError? {
print("onCancel")
self.eventSink = nil
return nil
}
}
Flutter code as below:
main.dart
<code>import 'package:flutter/material.dart';
import 'package:regrion_monitoring/location.dart';
WidgetsFlutterBinding.ensureInitialized();
class MainApp extends StatefulWidget {
const MainApp({super.key});
State<MainApp> createState() => _MainAppState();
class _MainAppState extends State<MainApp> {
requestLocationService().then((value) async {
await requestLocationPermission();
Widget build(BuildContext context) {
appBar: AppBar(title: const Text('Region Monitoring')),
child: Column(children: [
onPressed: () async => await LocationHelper().setRegions(),
child: const Text('Set Region')),
onPressed: () async => await LocationHelper().start(),
child: const Text('Start Monitor')),
onPressed: () async => await LocationHelper().stop(),
child: const Text('Stop Monitor'))
<code>import 'package:flutter/material.dart';
import 'package:regrion_monitoring/location.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MainApp());
}
class MainApp extends StatefulWidget {
const MainApp({super.key});
@override
State<MainApp> createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
@override
void initState() {
super.initState();
requestLocationService().then((value) async {
if (value) {
await requestLocationPermission();
}
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Region Monitoring')),
body: Center(
child: Column(children: [
FilledButton(
onPressed: () async => await LocationHelper().setRegions(),
child: const Text('Set Region')),
FilledButton(
onPressed: () async => await LocationHelper().start(),
child: const Text('Start Monitor')),
FilledButton(
onPressed: () async => await LocationHelper().stop(),
child: const Text('Stop Monitor'))
]))));
}
}
</code>
import 'package:flutter/material.dart';
import 'package:regrion_monitoring/location.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MainApp());
}
class MainApp extends StatefulWidget {
const MainApp({super.key});
@override
State<MainApp> createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
@override
void initState() {
super.initState();
requestLocationService().then((value) async {
if (value) {
await requestLocationPermission();
}
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Region Monitoring')),
body: Center(
child: Column(children: [
FilledButton(
onPressed: () async => await LocationHelper().setRegions(),
child: const Text('Set Region')),
FilledButton(
onPressed: () async => await LocationHelper().start(),
child: const Text('Start Monitor')),
FilledButton(
onPressed: () async => await LocationHelper().stop(),
child: const Text('Stop Monitor'))
]))));
}
}
location.dart
<code>import 'dart:async';
import 'package:flutter/services.dart';
import 'package:geolocator/geolocator.dart';
import 'package:location/location.dart' as location;
import 'package:permission_handler/permission_handler.dart' as permission;
static final LocationHelper _instance = LocationHelper._internal();
LocationHelper._internal();
factory LocationHelper() => _instance;
final MethodChannel channel = const MethodChannel('location');
final EventChannel eventChannel = const EventChannel('location_stream');
Future<void> start() async {
print('regrion monitoring: start');
await channel.invokeMethod('startMonitoring');
eventChannel.receiveBroadcastStream().listen((event) {
Map<String, dynamic> result = Map<String, dynamic>.from(event as Map);
print('receiveBroadcastStream');
print('regrion monitoring: identifier: ${result['identifier']}');
print('regrion monitoring: type: ${result['type']}');
Future<void> stop() async {
print('regrion monitoring: stop');
await channel.invokeMethod('stopMonitoring');
Future<void> setRegions() async {
Map<String, dynamic> regions = {
'longitude': 101.4466574,
'longitude': 101.4387836,
print('regrion monitoring: setRegions');
await channel.invokeMethod('setRegions', regions);
Future<bool> requestLocationService() async {
if (!await Geolocator.isLocationServiceEnabled()) {
if (Platform.isAndroid) {
await location.Location().requestService();
if (!await location.Location().serviceEnabled()) {
} else if (Platform.isIOS || Platform.isMacOS) {
await Geolocator.openLocationSettings();
Future<bool> requestLocationPermission() async {
if (await permission.Permission.locationWhenInUse.isDenied) {
await permission.Permission.locationWhenInUse.request();
if (await permission.Permission.locationWhenInUse.isGranted &&
!await permission.Permission.locationAlways.isGranted) {
if (Platform.isIOS || Platform.isMacOS) {
await permission.Permission.locationAlways.request();
if (await permission.Permission.locationAlways.isDenied) {
// if location permission is permanently denied
if (await permission.Permission.locationWhenInUse.isPermanentlyDenied) {
await permission.openAppSettings();
<code>import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:geolocator/geolocator.dart';
import 'package:location/location.dart' as location;
import 'package:permission_handler/permission_handler.dart' as permission;
class LocationHelper {
static final LocationHelper _instance = LocationHelper._internal();
LocationHelper._internal();
factory LocationHelper() => _instance;
final MethodChannel channel = const MethodChannel('location');
final EventChannel eventChannel = const EventChannel('location_stream');
Future<void> start() async {
print('regrion monitoring: start');
await channel.invokeMethod('startMonitoring');
eventChannel.receiveBroadcastStream().listen((event) {
Map<String, dynamic> result = Map<String, dynamic>.from(event as Map);
print('receiveBroadcastStream');
print('regrion monitoring: identifier: ${result['identifier']}');
print('regrion monitoring: type: ${result['type']}');
});
}
Future<void> stop() async {
print('regrion monitoring: stop');
await channel.invokeMethod('stopMonitoring');
}
Future<void> setRegions() async {
Map<String, dynamic> regions = {
'location1': {
'latitude': 3.1067979,
'longitude': 101.4466574,
'radius': 60,
'description': 'Lotus'
},
'location2': {
'latitude': 3.1072308,
'longitude': 101.4387836,
'radius': 60,
'description': 'NSK'
};
print('regrion monitoring: setRegions');
await channel.invokeMethod('setRegions', regions);
}
}
Future<bool> requestLocationService() async {
if (!await Geolocator.isLocationServiceEnabled()) {
if (Platform.isAndroid) {
await location.Location().requestService();
if (!await location.Location().serviceEnabled()) {
return false;
}
} else if (Platform.isIOS || Platform.isMacOS) {
await Geolocator.openLocationSettings();
}
}
return true;
}
Future<bool> requestLocationPermission() async {
if (await permission.Permission.locationWhenInUse.isDenied) {
await permission.Permission.locationWhenInUse.request();
}
if (await permission.Permission.locationWhenInUse.isGranted &&
!await permission.Permission.locationAlways.isGranted) {
if (Platform.isIOS || Platform.isMacOS) {
await permission.Permission.locationAlways.request();
}
if (await permission.Permission.locationAlways.isDenied) {
return false;
}
}
// if location permission is permanently denied
if (await permission.Permission.locationWhenInUse.isPermanentlyDenied) {
await permission.openAppSettings();
}
return true;
}
</code>
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:geolocator/geolocator.dart';
import 'package:location/location.dart' as location;
import 'package:permission_handler/permission_handler.dart' as permission;
class LocationHelper {
static final LocationHelper _instance = LocationHelper._internal();
LocationHelper._internal();
factory LocationHelper() => _instance;
final MethodChannel channel = const MethodChannel('location');
final EventChannel eventChannel = const EventChannel('location_stream');
Future<void> start() async {
print('regrion monitoring: start');
await channel.invokeMethod('startMonitoring');
eventChannel.receiveBroadcastStream().listen((event) {
Map<String, dynamic> result = Map<String, dynamic>.from(event as Map);
print('receiveBroadcastStream');
print('regrion monitoring: identifier: ${result['identifier']}');
print('regrion monitoring: type: ${result['type']}');
});
}
Future<void> stop() async {
print('regrion monitoring: stop');
await channel.invokeMethod('stopMonitoring');
}
Future<void> setRegions() async {
Map<String, dynamic> regions = {
'location1': {
'latitude': 3.1067979,
'longitude': 101.4466574,
'radius': 60,
'description': 'Lotus'
},
'location2': {
'latitude': 3.1072308,
'longitude': 101.4387836,
'radius': 60,
'description': 'NSK'
};
print('regrion monitoring: setRegions');
await channel.invokeMethod('setRegions', regions);
}
}
Future<bool> requestLocationService() async {
if (!await Geolocator.isLocationServiceEnabled()) {
if (Platform.isAndroid) {
await location.Location().requestService();
if (!await location.Location().serviceEnabled()) {
return false;
}
} else if (Platform.isIOS || Platform.isMacOS) {
await Geolocator.openLocationSettings();
}
}
return true;
}
Future<bool> requestLocationPermission() async {
if (await permission.Permission.locationWhenInUse.isDenied) {
await permission.Permission.locationWhenInUse.request();
}
if (await permission.Permission.locationWhenInUse.isGranted &&
!await permission.Permission.locationAlways.isGranted) {
if (Platform.isIOS || Platform.isMacOS) {
await permission.Permission.locationAlways.request();
}
if (await permission.Permission.locationAlways.isDenied) {
return false;
}
}
// if location permission is permanently denied
if (await permission.Permission.locationWhenInUse.isPermanentlyDenied) {
await permission.openAppSettings();
}
return true;
}
I have also set these two properties (Location Always and When In Use
and Location Always and When In Use
) in info.plist
I have also tried calling startMonitoring
in applicationWillTerminate
but it still doesn’t work. Can anyone help me?