Repository navigation
[Bug]: iOS - Permission.location.status / .isGranted can hang the main thread for 2+ seconds #1557
Description
Please check the following before submitting a new issue.
- I have searched the existing issues.
- I have carefully read the documentation and verified I have added the required platform specific configuration.
Please select affected platform(s)
- Android
- iOS
- Windows
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
[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
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!
Activity
yuriibezhevets commentedon Sep 25, 2026
We are seeing the same underlying issue in production through
Permission.locationWhenInUse.request().Here is the relevant main-thread stack trace captured by Sentry:
This confirms that
requestPermissionalso performs the synchronousCLLocationManager.authorizationStatuslookup on the mainthread. When Core Location takes multiple seconds to respond, the UI remains blocked for the entire duration.
The proposed change to
PermissionManager.checkPermissionStatusappears to cover.statusand.isGranted, but may not cover this path becauseLocationPermissionStrategy.requestPermissionperforms its own status check.Could the fix also move the authorization status lookup inside
requestPermissionoff the main thread?Create the location manager after the status read; read notification …