This is a problem only happens in a release app
I am experiencing issue that when I try to request for Location When In Use permission on iOS, the dialog pop up and close suddenly before user click the buttons
I have research that, this might be caused by iOS ARC.
https://stackoverflow.com/questions/27048995/alert-view-disappears-on-its-own-when-calling-locationmanager-requestwheninusea
I checked the source code, locationManager is already a strong reference in LocationWhenInUse class. Then I tried to make handler in RNPermission.m to a member variable of the class instead of a local variable in request(). The problem solved immediately.
Though, I am not an expert of iOS, I think my fix is not a proper fix, thus I hope someone could help on fixing this.
React native info output:
System:
OS: macOS 10.15.1
CPU: (12) x64 Intel(R) Core(TM) i9-8950HK CPU @ 2.90GHz
Memory: 3.34 GB / 32.00 GB
Shell: 5.7.1 - /bin/zsh
Binaries:
Node: 12.13.0 - ~/.nvm/versions/node/v12.13.0/bin/node
npm: 6.12.0 - ~/.nvm/versions/node/v12.13.0/bin/npm
SDKs:
iOS SDK:
Platforms: iOS 13.2, DriverKit 19.0, macOS 10.15, tvOS 13.2, watchOS 6.1
IDEs:
Android Studio: 3.5 AI-191.8026.42.35.5977832
Xcode: 11.2.1/11B500 - /usr/bin/xcodebuild
npmPackages:
@react-native-community/cli: 2.9.0 => 2.9.0
react: 16.9.0 => 16.9.0
react-native: 0.61.5 => 0.61.5
npmGlobalPackages:
react-native-cli: 2.0.1
Library version: 2.0.8
Describe what you expected to happen:
Permission.request(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE)
I have made a patch that fixed the issue but I am not sure whether it is a good fix
diff --git a/node_modules/react-native-permissions/ios/RNPermissions.h b/node_modules/react-native-permissions/ios/RNPermissions.h
index 3314426..921d129 100644
--- a/node_modules/react-native-permissions/ios/RNPermissions.h
+++ b/node_modules/react-native-permissions/ios/RNPermissions.h
@@ -79,6 +79,8 @@ typedef enum {
@interface RNPermissions : NSObject <RCTBridgeModule>
+@property (nonatomic, strong, nullable) id<RNPermissionHandler> handler;
+
+ (bool)isFlaggedAsRequested:(NSString * _Nonnull)handlerId;
+ (void)flagAsRequested:(NSString * _Nonnull)handlerId;
diff --git a/node_modules/react-native-permissions/ios/RNPermissions.m b/node_modules/react-native-permissions/ios/RNPermissions.m
index 7affed9..6939e29 100644
--- a/node_modules/react-native-permissions/ios/RNPermissions.m
+++ b/node_modules/react-native-permissions/ios/RNPermissions.m
@@ -116,83 +116,83 @@ - (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}
-- (id<RNPermissionHandler> _Nullable)handlerForPermission:(RNPermission)permission {
- id<RNPermissionHandler> handler = nil;
+- (void)handlerForPermission:(RNPermission)permission {
+ self.handler = nil;
switch (permission) {
#if __has_include("RNPermissionHandlerBluetoothPeripheral.h")
case RNPermissionBluetoothPeripheral:
- handler = [RNPermissionHandlerBluetoothPeripheral new];
+ self.handler = [RNPermissionHandlerBluetoothPeripheral new];
break;
#endif
#if __has_include("RNPermissionHandlerCalendars.h")
case RNPermissionCalendars:
- handler = [RNPermissionHandlerCalendars new];
+ self.handler = [RNPermissionHandlerCalendars new];
break;
#endif
#if __has_include("RNPermissionHandlerCamera.h")
case RNPermissionCamera:
- handler = [RNPermissionHandlerCamera new];
+ self.handler = [RNPermissionHandlerCamera new];
break;
#endif
#if __has_include("RNPermissionHandlerContacts.h")
case RNPermissionContacts:
- handler = [RNPermissionHandlerContacts new];
+ self.handler = [RNPermissionHandlerContacts new];
break;
#endif
#if __has_include("RNPermissionHandlerFaceID.h")
case RNPermissionFaceID:
- handler = [RNPermissionHandlerFaceID new];
+ self.handler = [RNPermissionHandlerFaceID new];
break;
#endif
#if __has_include("RNPermissionHandlerLocationAlways.h")
case RNPermissionLocationAlways:
- handler = [RNPermissionHandlerLocationAlways new];
+ self.handler = [RNPermissionHandlerLocationAlways new];
break;
#endif
#if __has_include("RNPermissionHandlerLocationWhenInUse.h")
case RNPermissionLocationWhenInUse:
- handler = [RNPermissionHandlerLocationWhenInUse new];
+ self.handler = [RNPermissionHandlerLocationWhenInUse new];
break;
#endif
#if __has_include("RNPermissionHandlerMediaLibrary.h")
case RNPermissionMediaLibrary:
- handler = [RNPermissionHandlerMediaLibrary new];
+ self.handler = [RNPermissionHandlerMediaLibrary new];
break;
#endif
#if __has_include("RNPermissionHandlerMicrophone.h")
case RNPermissionMicrophone:
- handler = [RNPermissionHandlerMicrophone new];
+ self.handler = [RNPermissionHandlerMicrophone new];
break;
#endif
#if __has_include("RNPermissionHandlerMotion.h")
case RNPermissionMotion:
- handler = [RNPermissionHandlerMotion new];
+ self.handler = [RNPermissionHandlerMotion new];
break;
#endif
#if __has_include("RNPermissionHandlerPhotoLibrary.h")
case RNPermissionPhotoLibrary:
- handler = [RNPermissionHandlerPhotoLibrary new];
+ self.handler = [RNPermissionHandlerPhotoLibrary new];
break;
#endif
#if __has_include("RNPermissionHandlerReminders.h")
case RNPermissionReminders:
- handler = [RNPermissionHandlerReminders new];
+ self.handler = [RNPermissionHandlerReminders new];
break;
#endif
#if __has_include("RNPermissionHandlerSiri.h")
case RNPermissionSiri:
- handler = [RNPermissionHandlerSiri new];
+ self.handler = [RNPermissionHandlerSiri new];
break;
#endif
#if __has_include("RNPermissionHandlerSpeechRecognition.h")
case RNPermissionSpeechRecognition:
- handler = [RNPermissionHandlerSpeechRecognition new];
+ self.handler = [RNPermissionHandlerSpeechRecognition new];
break;
#endif
#if __has_include("RNPermissionHandlerStoreKit.h")
case RNPermissionStoreKit:
- handler = [RNPermissionHandlerStoreKit new];
+ self.handler = [RNPermissionHandlerStoreKit new];
break;
#endif
case RNPermissionUnknown:
@@ -200,15 +200,13 @@ - (dispatch_queue_t)methodQueue {
}
#if RCT_DEV
- for (NSString *key in [[handler class] usageDescriptionKeys]) {
+ for (NSString *key in [[self.handler class] usageDescriptionKeys]) {
if (![[NSBundle mainBundle] objectForInfoDictionaryKey:key]) {
RCTLogError(@"Cannot check or request permission without the required \"%@\" entry in your app \"Info.plist\" file", key);
- return nil;
+ self.handler = nil;
}
}
#endif
-
- return handler;
}
- (NSString *)stringForStatus:(RNPermissionStatus)status {
@@ -269,14 +267,14 @@ + (void)flagAsRequested:(NSString * _Nonnull)handlerId {
checkWithPermission:(RNPermission)permission
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
- id<RNPermissionHandler> handler = [self handlerForPermission:permission];
+ [self handlerForPermission:permission];
- [handler checkWithResolver:^(RNPermissionStatus status) {
+ [self.handler checkWithResolver:^(RNPermissionStatus status) {
NSString *strStatus = [self stringForStatus:status];
- NSLog(@"[react-native-permissions] %@ permission checked: %@", [[handler class] handlerUniqueId], strStatus);
+ NSLog(@"[react-native-permissions] %@ permission checked: %@", [[self.handler class] handlerUniqueId], strStatus);
resolve(strStatus);
} rejecter:^(NSError *error) {
- NSLog(@"[react-native-permissions] %@ permission failed: %@", [[handler class] handlerUniqueId], error.localizedDescription);
+ NSLog(@"[react-native-permissions] %@ permission failed: %@", [[self.handler class] handlerUniqueId], error.localizedDescription);
reject([NSString stringWithFormat:@"%ld", (long)error.code], error.localizedDescription, error);
}];
}
@@ -285,14 +283,14 @@ + (void)flagAsRequested:(NSString * _Nonnull)handlerId {
requestWithPermission:(RNPermission)permission
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
- id<RNPermissionHandler> handler = [self handlerForPermission:permission];
+ [self handlerForPermission:permission];
- [handler requestWithResolver:^(RNPermissionStatus status) {
+ [self.handler requestWithResolver:^(RNPermissionStatus status) {
NSString *strStatus = [self stringForStatus:status];
- NSLog(@"[react-native-permissions] %@ permission checked: %@", [[handler class] handlerUniqueId], strStatus);
+ NSLog(@"[react-native-permissions] %@ permission checked: %@", [[self.handler class] handlerUniqueId], strStatus);
resolve(strStatus);
} rejecter:^(NSError *error) {
- NSLog(@"[react-native-permissions] %@ permission failed: %@", [[handler class] handlerUniqueId], error.localizedDescription);
+ NSLog(@"[react-native-permissions] %@ permission failed: %@", [[self.handler class] handlerUniqueId], error.localizedDescription);
reject([NSString stringWithFormat:@"%ld", (long)error.code], error.localizedDescription, error);
}];
}
@booker-dragon Indeed, this happens because of ARC being a little too aggressive. But the solution is too fragile and will not work with concurrent checks / requests.
A solution could be a NSMutableArray<id<RNPermissionHandler>>, then adding handlers instances in it / set them to nil when the job is done (using the array index)
It's available: https://github.com/react-native-community/react-native-permissions/releases/tag/2.0.9
I added identical logical for notification permission. Thanks again!
Most helpful comment
@booker-dragon Indeed, this happens because of ARC being a little too aggressive. But the solution is too fragile and will not work with concurrent checks / requests.
A solution could be a
NSMutableArray<id<RNPermissionHandler>>, then adding handlers instances in it / set them tonilwhen the job is done (using the array index)