Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Repeated requests for iOS Notifications with other options are not possible #20072

Open
below opened this issue Nov 16, 2022 · 3 comments
Open

Comments

@below
Copy link

below commented Nov 16, 2022

Summary

On iOS, the permissions for Notification can be requested with a set of options. The most common ones are to display alerts or play sounds, but they can also enable notifications on CarPlay displays, or enable critical alerts. Over the course of its lifetime, an app may introduce aditional capabilities, such as critical alerts or other, newly introduced iOS features and thus, must call requestAuthorization(options:completionHandler:) again with a new set of options. Expo Notifications supports all these options for requestPermissionsAsync.

The issue is, that after an app is newly installed on a device, the first call to Notification.requestPermissionsAsync will prompt the user, and set the given options. However, subsequent calls will have no effect.
This means, for the user to take advantage of new features, they will have to de-install and re-install the app, which is completely unacceptable.

The Root of the Problem

The root of the problem can be found in EXPermissionsService.m:111:

  BOOL isGranted = [EXPermissionsService statusForPermission:permission] == EXPermissionStatusGranted;
  permission[@"granted"] = @(isGranted);
  
  if (isGranted) {
    return onResult(permission);
  }
  
  [self askForGlobalPermissionUsingRequesterClass:requesterClass 
                                     withResolver:onResult 
                                     withRejecter:reject];

This means that permissions are only ever requested again, if permission has not previously been granted. This however does not consider different options, but only if any sort of notification permissions have been granted. This means that if previously only Alerts have been requested, a new request for Alerts and Sounds will be ignored.

The Solution

On iOS, it is perfectly acceptable to call requestAuthorization(options:completionHandler:) repeatedly:

subsequent calls to this method (even with different options do not prompt the user again. It is best practice on iOS to call this method liberally (e.g. at every startup), and the performance hit should not be different from getting the permission status.

Also not that several tutorials suggest to check the permission status first, and then requesting it. This is — at least for iOS — not advisable.

My proposal therefore is to remove the method askForPermissionUsingRequesterClass entirely, and instead rename askForGlobalPermissionUsingRequesterClass appropriately.

I will gladly file an appropriate pull request

What platform(s) does this occur on?

iOS

Environment

expo-env-info 1.0.5 environment info:
System:
OS: macOS 13.0.1
Shell: 5.8.1 - /bin/zsh
Binaries:
Node: 18.7.0 - /opt/homebrew/bin/node
npm: 8.15.0 - /opt/homebrew/bin/npm
Managers:
CocoaPods: 1.11.3 - /opt/homebrew/bin/pod
SDKs:
iOS SDK:
Platforms: DriverKit 22.1, iOS 16.1, macOS 13.0, tvOS 16.1, watchOS 9.1
IDEs:
Android Studio: 2021.3 AI-213.7172.25.2113.9123335
Xcode: 14.1/14B47b - /usr/bin/xcodebuild
npmPackages:
expo: ~47.0.3 => 47.0.3
react: 18.1.0 => 18.1.0
react-native: 0.70.5 => 0.70.5
npmGlobalPackages:
expo-cli: 5.3.0
Expo Workflow: bare

Minimal reproducible example

A minimal reproducible example can be found here

@below below added the needs validation Issue needs to be validated label Nov 16, 2022
below added a commit to below/expo that referenced this issue Nov 16, 2022
… options expo#20072

On iOS, the permissions for Notification can be requested with a set of [options](https://developer.apple.com/documentation/usernotifications/unauthorizationoptions). The most common ones are to display alerts or play sounds, but they can also enable notifications on CarPlay displays, or enable critical alerts. Over the course of its lifetime, an app may introduce aditional capabilities, such as critical alerts or other, newly introduced iOS features and thus, must call `requestAuthorization(options:completionHandler:)` again with a new set of options. Expo Notifications supports all these [options](https://docs.expo.dev/versions/latest/sdk/notifications/#requestpermissionsasyncrequest-notificationpermissionsrequest-promisenotificationpermissionsstatus) for  `requestPermissionsAsync`.

The issue is, that after an app is newly installed on a device, the first call to `Notification.requestPermissionsAsync` will prompt the user, and set the given options. **However, subsequent calls will have no effect.**
This means, for the user to take advantage of new features, they will have to de-install and re-install the app, which is completely unacceptable.

The root of the problem can be found in [EXPermissionsService.m:111](https://github.com/expo/expo/blob/168ee43f71f005baa11edf98e518593443e1807a/packages/expo-modules-core/ios/Services/Permissions/EXPermissionsService.m#L111):

```objc
  BOOL isGranted = [EXPermissionsService statusForPermission:permission] == EXPermissionStatusGranted;
  permission[@"granted"] = @(isGranted);

  if (isGranted) {
    return onResult(permission);
  }

  [self askForGlobalPermissionUsingRequesterClass:requesterClass
                                     withResolver:onResult
                                     withRejecter:reject];
```

This means that permissions are only ever requested again, if permission has not previously been granted. This however **does not consider different options**, but only if any sort of notification permissions have been granted. This means that if previously only Alerts have been requested, a new request for Alerts and Sounds will be ignored.

On iOS, it is perfectly acceptable to call `requestAuthorization(options:completionHandler:)` repeatedly:
> subsequent calls to this method (even with different options do not prompt the user again. It is best practice on iOS to call this method liberally (e.g. at every startup), and the performance hit should not be different from getting the permission status.

Also not that several tutorials suggest to check the permission status first, and then requesting it. This is — at least for iOS — not advisable.

Therefore the method `askForPermissionUsingRequesterClass` was removed entirely, and instead the method `askForGlobalPermissionUsingRequesterClass` was renamed appropriately.
below added a commit to below/expo that referenced this issue Nov 17, 2022
… options expo#20072

On iOS, the permissions for Notification can be requested with a set of [options](https://developer.apple.com/documentation/usernotifications/unauthorizationoptions). The most common ones are to display alerts or play sounds, but they can also enable notifications on CarPlay displays, or enable critical alerts. Over the course of its lifetime, an app may introduce aditional capabilities, such as critical alerts or other, newly introduced iOS features and thus, must call `requestAuthorization(options:completionHandler:)` again with a new set of options. Expo Notifications supports all these [options](https://docs.expo.dev/versions/latest/sdk/notifications/#requestpermissionsasyncrequest-notificationpermissionsrequest-promisenotificationpermissionsstatus) for  `requestPermissionsAsync`.

The issue is, that after an app is newly installed on a device, the first call to `Notification.requestPermissionsAsync` will prompt the user, and set the given options. **However, subsequent calls will have no effect.**
This means, for the user to take advantage of new features, they will have to de-install and re-install the app, which is completely unacceptable.

The root of the problem can be found in [EXPermissionsService.m:111](https://github.com/expo/expo/blob/168ee43f71f005baa11edf98e518593443e1807a/packages/expo-modules-core/ios/Services/Permissions/EXPermissionsService.m#L111):

```objc
  BOOL isGranted = [EXPermissionsService statusForPermission:permission] == EXPermissionStatusGranted;
  permission[@"granted"] = @(isGranted);

  if (isGranted) {
    return onResult(permission);
  }

  [self askForGlobalPermissionUsingRequesterClass:requesterClass
                                     withResolver:onResult
                                     withRejecter:reject];
```

This means that permissions are only ever requested again, if permission has not previously been granted. This however **does not consider different options**, but only if any sort of notification permissions have been granted. This means that if previously only Alerts have been requested, a new request for Alerts and Sounds will be ignored.

On iOS, it is perfectly acceptable to call `requestAuthorization(options:completionHandler:)` repeatedly:
> subsequent calls to this method (even with different options do not prompt the user again. It is best practice on iOS to call this method liberally (e.g. at every startup), and the performance hit should not be different from getting the permission status.

Also not that several tutorials suggest to check the permission status first, and then requesting it. This is — at least for iOS — not advisable.

Therefore the method `askForPermissionUsingRequesterClass` was removed entirely, and instead the method `askForGlobalPermissionUsingRequesterClass` was renamed appropriately.

This was tested with my [test
app](https://github.com/below/ExpoNotificationIssue).
@below
Copy link
Author

below commented Nov 17, 2022

A pull request for this issue can be found here: #20086

below added a commit to below/expo that referenced this issue Nov 17, 2022
… options expo#20072

On iOS, the permissions for Notification can be requested with a set of [options](https://developer.apple.com/documentation/usernotifications/unauthorizationoptions). The most common ones are to display alerts or play sounds, but they can also enable notifications on CarPlay displays, or enable critical alerts. Over the course of its lifetime, an app may introduce aditional capabilities, such as critical alerts or other, newly introduced iOS features and thus, must call `requestAuthorization(options:completionHandler:)` again with a new set of options. Expo Notifications supports all these [options](https://docs.expo.dev/versions/latest/sdk/notifications/#requestpermissionsasyncrequest-notificationpermissionsrequest-promisenotificationpermissionsstatus) for  `requestPermissionsAsync`.

The issue is, that after an app is newly installed on a device, the first call to `Notification.requestPermissionsAsync` will prompt the user, and set the given options. **However, subsequent calls will have no effect.**
This means, for the user to take advantage of new features, they will have to de-install and re-install the app, which is completely unacceptable.

The root of the problem can be found in [EXPermissionsService.m:111](https://github.com/expo/expo/blob/168ee43f71f005baa11edf98e518593443e1807a/packages/expo-modules-core/ios/Services/Permissions/EXPermissionsService.m#L111):

```objc
  BOOL isGranted = [EXPermissionsService statusForPermission:permission] == EXPermissionStatusGranted;
  permission[@"granted"] = @(isGranted);

  if (isGranted) {
    return onResult(permission);
  }

  [self askForGlobalPermissionUsingRequesterClass:requesterClass
                                     withResolver:onResult
                                     withRejecter:reject];
```

This means that permissions are only ever requested again, if permission has not previously been granted. This however **does not consider different options**, but only if any sort of notification permissions have been granted. This means that if previously only Alerts have been requested, a new request for Alerts and Sounds will be ignored.

On iOS, it is perfectly acceptable to call `requestAuthorization(options:completionHandler:)` repeatedly:
> subsequent calls to this method (even with different options do not prompt the user again. It is best practice on iOS to call this method liberally (e.g. at every startup), and the performance hit should not be different from getting the permission status.

Also not that several tutorials suggest to check the permission status first, and then requesting it. This is — at least for iOS — not advisable.

Therefore the method `askForPermissionUsingRequesterClass` was removed entirely, and instead the method `askForGlobalPermissionUsingRequesterClass` was renamed appropriately.

This was tested with my [test
app](https://github.com/below/ExpoNotificationIssue).
below added a commit to below/expo that referenced this issue Nov 17, 2022
… options expo#20072

On iOS, the permissions for Notification can be requested with a set of [options](https://developer.apple.com/documentation/usernotifications/unauthorizationoptions). The most common ones are to display alerts or play sounds, but they can also enable notifications on CarPlay displays, or enable critical alerts. Over the course of its lifetime, an app may introduce aditional capabilities, such as critical alerts or other, newly introduced iOS features and thus, must call `requestAuthorization(options:completionHandler:)` again with a new set of options. Expo Notifications supports all these [options](https://docs.expo.dev/versions/latest/sdk/notifications/#requestpermissionsasyncrequest-notificationpermissionsrequest-promisenotificationpermissionsstatus) for  `requestPermissionsAsync`.

The issue is, that after an app is newly installed on a device, the first call to `Notification.requestPermissionsAsync` will prompt the user, and set the given options. **However, subsequent calls will have no effect.**
This means, for the user to take advantage of new features, they will have to de-install and re-install the app, which is completely unacceptable.

The root of the problem can be found in [EXPermissionsService.m:111](https://github.com/expo/expo/blob/168ee43f71f005baa11edf98e518593443e1807a/packages/expo-modules-core/ios/Services/Permissions/EXPermissionsService.m#L111):

```objc
  BOOL isGranted = [EXPermissionsService statusForPermission:permission] == EXPermissionStatusGranted;
  permission[@"granted"] = @(isGranted);

  if (isGranted) {
    return onResult(permission);
  }

  [self askForGlobalPermissionUsingRequesterClass:requesterClass
                                     withResolver:onResult
                                     withRejecter:reject];
```

This means that permissions are only ever requested again, if permission has not previously been granted. This however **does not consider different options**, but only if any sort of notification permissions have been granted. This means that if previously only Alerts have been requested, a new request for Alerts and Sounds will be ignored.

On iOS, it is perfectly acceptable to call `requestAuthorization(options:completionHandler:)` repeatedly:
> subsequent calls to this method (even with different options do not prompt the user again. It is best practice on iOS to call this method liberally (e.g. at every startup), and the performance hit should not be different from getting the permission status.

Also not that several tutorials suggest to check the permission status first, and then requesting it. This is — at least for iOS — not advisable.

Therefore the method `askForPermissionUsingRequesterClass` was removed entirely, and instead the method `askForGlobalPermissionUsingRequesterClass` was renamed appropriately.

This was tested with my [test
app](https://github.com/below/ExpoNotificationIssue).
@brentvatne brentvatne added Issue accepted and removed needs validation Issue needs to be validated labels Nov 25, 2022
@brentvatne
Copy link
Member

thank you for the PR, @below!

@expo-bot
Copy link
Collaborator

Thank you for filing this issue!
This comment acknowledges we believe this may be a bug and there’s enough information to investigate it.
However, we can’t promise any sort of timeline for resolution. We prioritize issues based on severity, breadth of impact, and alignment with our roadmap. If you’d like to help move it more quickly, you can continue to investigate it more deeply and/or you can open a pull request that fixes the cause.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants