Please check the following before submitting a new issue.
Please select affected platform(s)
Steps to reproduce
We collect production crash/ANR data via Sentry (App Hang detection, watchdog
threshold 2000ms). We see repeated "App Hanging" events on iOS whose main
thread stack trace is:
App Hanging: App hanging for at least 2000 ms.
Runner 0x100794114 +[LocationPermissionStrategy permissionStatus:] (LocationPermissionStrategy.m:166)
Runner 0x100792ec0 +[PermissionManager checkPermissionStatus:result:] (PermissionManager.m:23)
Runner 0x100792a70 -[PermissionHandlerPlugin handleMethodCall:result:] (PermissionHandlerPlugin.m:30)
Flutter 0x101a7dce4 __45-[FlutterMethodChannel setMethodCallHandler:]_block_invoke (FlutterChannels.mm:315)
Flutter 0x1015dbdf0 flutter::PlatformMessageHandlerIos::HandlePlatformMessage (platform_message_handler_ios.mm:70)
This happens most frequently right at applicationDidBecomeActive /
AppLifecycleState.resumed, when app code calls Permission.location.status
or Permission.location.isGranted to detect whether the user changed the
location permission in Settings while the app was backgrounded.
Root cause: LocationPermissionStrategy.checkPermissionStatus: calls
+[CLLocationManager authorizationStatus] synchronously on whatever thread
invoked the method channel (the main/platform thread by default), with no
dispatch_async off-thread hop.
This is the same class of problem as #1002, which was fixed for
checkServiceStatus (CLLocationManager.locationServicesEnabled) in #1329 /
permission_handler_apple 9.4.5:
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL isEnabled = [CLLocationManager locationServicesEnabled];
dispatch_async(dispatch_get_main_queue(), ^(void) {
completionHandler(isEnabled ? ServiceStatusEnabled : ServiceStatusDisabled);
});
});
}
However, checkPermissionStatus: (used by .status / .isGranted) was never given the same treatment and is still synchronous today in the latest published version (9.6.1):
- (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission {
return [LocationPermissionStrategy permissionStatus:permission];
}
+ (PermissionStatus)permissionStatus:(PermissionGroup)permission {
CLAuthorizationStatus authorizationStatus = [CLLocationManager authorizationStatus]; // synchronous, can block
...
}
Since PermissionManager.checkPermissionStatus:result: calls this synchronously and immediately passes the result to FlutterResult, the main thread is blocked for however long authorizationStatus takes internally (observed 2000ms+ on affected devices/iOS versions in production).
Possible fix
Mirroring the checkServiceStatus fix, checkPermissionStatus could dispatch the location-specific call off the main thread and hop back before invoking the result callback, e.g. (scoped to location permission groups only, to avoid touching the other ~10 permission strategies that don't have this issue):
+ (void)checkPermissionStatus:(enum PermissionGroup)permission result:(FlutterResult)result {
id<PermissionStrategy> permissionStrategy = [PermissionManager permissionStrategy:permission];
if (permission == PermissionGroupLocation || permission == PermissionGroupLocationAlways ||
permission == PermissionGroupLocationWhenInUse) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
PermissionStatus status = [permissionStrategy checkPermissionStatus:permission];
dispatch_async(dispatch_get_main_queue(), ^{ result(@(status)); });
});
return;
}
PermissionStatus status = [permissionStrategy checkPermissionStatus:permission];
result(@(status));
}
Expected results
Permission.location.status / .isGranted should never be able to block the calling (main) thread for multiple seconds, consistent with how serviceStatus already behaves after #1329.
Actual results
Main thread hangs 2000ms+ inside [CLLocationManager authorizationStatus], reported by Sentry's App Hang detector on iOS, primarily right after the app becomes active again.
Code sample
Code sample
Screenshots or video
Screenshots or video demonstration
[Upload media here]
Version
permission_handler: 13.0.1 / permission_handler_apple: 9.6.1
Flutter Doctor output
Doctor output
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.44.8, on macOS 26.5.2 25F84 darwin-arm64, locale de-AT)
[✓] Android toolchain - develop for Android devices (Android SDK version 37.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 26.6)
[✓] Chrome - develop for the web
[✓] Connected device (2 available)
[✓] Network resources
• No issues found!
Please check the following before submitting a new issue.
Please select affected platform(s)
Steps to reproduce
We collect production crash/ANR data via Sentry (App Hang detection, watchdog
threshold 2000ms). We see repeated "App Hanging" events on iOS whose main
thread stack trace is:
This happens most frequently right at
applicationDidBecomeActive/AppLifecycleState.resumed, when app code callsPermission.location.statusor
Permission.location.isGrantedto detect whether the user changed thelocation permission in Settings while the app was backgrounded.
Root cause:
LocationPermissionStrategy.checkPermissionStatus:calls+[CLLocationManager authorizationStatus]synchronously on whatever threadinvoked the method channel (the main/platform thread by default), with no
dispatch_asyncoff-thread hop.This is the same class of problem as #1002, which was fixed for
checkServiceStatus(CLLocationManager.locationServicesEnabled) in #1329 /permission_handler_apple9.4.5:However, checkPermissionStatus: (used by .status / .isGranted) was never given the same treatment and is still synchronous today in the latest published version (9.6.1):
Since PermissionManager.checkPermissionStatus:result: calls this synchronously and immediately passes the result to FlutterResult, the main thread is blocked for however long authorizationStatus takes internally (observed 2000ms+ on affected devices/iOS versions in production).
Possible fix
Mirroring the checkServiceStatus fix, checkPermissionStatus could dispatch the location-specific call off the main thread and hop back before invoking the result callback, e.g. (scoped to location permission groups only, to avoid touching the other ~10 permission strategies that don't have this issue):
Expected results
Permission.location.status / .isGranted should never be able to block the calling (main) thread for multiple seconds, consistent with how serviceStatus already behaves after #1329.
Actual results
Main thread hangs 2000ms+ inside [CLLocationManager authorizationStatus], reported by Sentry's App Hang detector on iOS, primarily right after the app becomes active again.
Code sample
Code sample
[Paste your code here]Screenshots or video
Screenshots or video demonstration
[Upload media here]
Version
permission_handler: 13.0.1 / permission_handler_apple: 9.6.1
Flutter Doctor output
Doctor output