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

Location full accuracy (issue #489) #503

Merged
merged 8 commits into from
Oct 10, 2020

Conversation

adapptor-kurt
Copy link
Contributor

Adds a LocationFullAccuracy pod for checking and requesting (temporary) full accuracy on iOS 14. Addresses issue #489.

Updates the request rationale to require a temporaryPurposeKey for this permission. I experimented with adding a separate function for this temporary permission, but I think this solution is more elegant. Feedback welcome.

Test Plan

Test on an iOS 14 beta 3 device in the example app with combinations of when-in-use/background and full/reduced accuracy permissions.

What's required for testing (prerequisites)?

  • iOS 14 beta 3
  • Xcode Version 12.0 beta 3

What are the steps to reproduce (after prerequisites)?

Compatibility

OS Implemented
iOS
Android

Checklist

  • I have tested this on a device and a simulator
  • I added the documentation in README.md
  • I mentioned this change in CHANGELOG.md
  • I updated the typed files (TS and Flow)
  • I added a sample use of the API in the example project (example/App.js)

@adapptor-kurt adapptor-kurt requested a review from zoontek as a code owner August 28, 2020 06:13
@jaspermeijaard
Copy link

I think this will be a very good addition to the package 👍 🎉 One thing I found a bit inconvenient to work with is the way the required/optional rationele are implemented and break current implementations. Example of an implementation that now breaks:

async function askCameraPermissions() {
    const permission: "android.permission.CAMERA" | "ios.permission.CAMERA" = Platform.select({
        android: Permissions.PERMISSIONS.ANDROID.CAMERA,
        ios: Permissions.PERMISSIONS.IOS.CAMERA,
    })!;

    // Variable permission is not allowed as argument. Type error: "No overload matches this call."
    const result = await Permissions.request(permission); 

    return result === "granted";
}

Typescript output:

error TS2769: No overload matches this call.
  Overload 1 of 3, '(permission: Values<Readonly<{ ACCEPT_HANDOVER: "android.permission.ACCEPT_HANDOVER"; ACCESS_BACKGROUND_LOCATION: "android.permission.ACCESS_BACKGROUND_LOCATION"; ACCESS_COARSE_LOCATION: "android.permission.ACCESS_COARSE_LOCATION"; ... 25 more ...; WRITE_EXTERNAL_STORAGE: "android.permission.WRITE_EXTERNAL_STORAGE"; }>>, rationale?: Rationale | undefined): Promise<...>', gave the following error.
    Argument of type '"android.permission.CAMERA" | "ios.permission.CAMERA"' is not assignable to parameter of type 'Values<Readonly<{ ACCEPT_HANDOVER: "android.permission.ACCEPT_HANDOVER"; ACCESS_BACKGROUND_LOCATION: "android.permission.ACCESS_BACKGROUND_LOCATION"; ACCESS_COARSE_LOCATION: "android.permission.ACCESS_COARSE_LOCATION"; ... 25 more ...; WRITE_EXTERNAL_STORAGE: "android.permission.WRITE_EXTERNAL_STORAGE"; }>>'.
      Type '"ios.permission.CAMERA"' is not assignable to type 'Values<Readonly<{ ACCEPT_HANDOVER: "android.permission.ACCEPT_HANDOVER"; ACCESS_BACKGROUND_LOCATION: "android.permission.ACCESS_BACKGROUND_LOCATION"; ACCESS_COARSE_LOCATION: "android.permission.ACCESS_COARSE_LOCATION"; ... 25 more ...; WRITE_EXTERNAL_STORAGE: "android.permission.WRITE_EXTERNAL_STORAGE"; }>>'.
  Overload 2 of 3, '(permission: "ios.permission.CAMERA" | "ios.permission.APP_TRACKING_TRANSPARENCY" | "ios.permission.BLUETOOTH_PERIPHERAL" | "ios.permission.CALENDARS" | "ios.permission.CONTACTS" | ... 10 more ... | "ios.permission.STOREKIT"): Promise<...>', gave the following error.
    Argument of type '"android.permission.CAMERA" | "ios.permission.CAMERA"' is not assignable to parameter of type '"ios.permission.CAMERA" | "ios.permission.APP_TRACKING_TRANSPARENCY" | "ios.permission.BLUETOOTH_PERIPHERAL" | "ios.permission.CALENDARS" | "ios.permission.CONTACTS" | ... 10 more ... | "ios.permission.STOREKIT"'.
      Type '"android.permission.CAMERA"' is not assignable to type '"ios.permission.CAMERA" | "ios.permission.APP_TRACKING_TRANSPARENCY" | "ios.permission.BLUETOOTH_PERIPHERAL" | "ios.permission.CALENDARS" | "ios.permission.CONTACTS" | ... 10 more ... | "ios.permission.STOREKIT"'.

1298     const permissions = await Permissions.request(permission);
                                                       ~~~~~~~~~~

Any suggestion how to request permissions in an elegant way cross-platform?

@zoontek
Copy link
Owner

zoontek commented Aug 31, 2020

It's great, but we need to find a better API than this one.

…dd informative error when purposeKey is not specified.
@adapptor-kurt
Copy link
Contributor Author

I restored the definition of request so that it doesn't break anything else. But since there's no typing to enforce temporaryPurposeKey I added a runtime error when LOCATION_FULL_ACCURACY is requested without it.

@zoontek happy to change this around if you have some suggestions. I also agree that it's not going to win any API design awards.

@zoontek
Copy link
Owner

zoontek commented Sep 1, 2020

Another solution would be making a breaking change (3.0.0) and enforcing usage of TypeScript 4 (with labelled tuples - https://devblogs.microsoft.com/typescript/announcing-typescript-4-0/). It would also allow the renaming of checkNotifications / requestNotifications.

But this version juste came out, it's a bit extreme.

@adapptor-kurt
Copy link
Contributor Author

@zoontek Are the new labelled tuples any different from function overloads that you can already do in TypeScript? Either way, I think a separate function is better for check/requestNotifications because it's easier to document and understand for users of the package. Function overloads are confusing, as demonstrated by me stuffing up the original version of this PR.

I had tried adding a new requestLocationTemporaryFullAccuracy function, but it seemed overkill for this iOS-only beta functionality. The temporary purpose key also seemed to match the "rationale" parameter. If Android had similar precision permissions, with the need for additional options, it might be worth it.

@zoontek
Copy link
Owner

zoontek commented Sep 1, 2020

@adapptor-kurt It can be used like this.

The confusion came from the mix-up with the Android rationale, which are not options but messages displayed to the user.

@adapptor-kurt
Copy link
Contributor Author

@zoontek The overload version is like this.

I did have it like this, but made a mistake that broke @jaspermeijaard's case.

@adapptor-kurt
Copy link
Contributor Author

temporaryPurposeKey is a reference to a message defined in the plist. You can argue that this is also not an option, but a message displayed to the user.

@adapptor-kurt
Copy link
Contributor Author

@zoontek I'd like to get this in, but if you have reservations about the API then I'm happy to make any suggested changes. I think these are the choices:

  1. Use check/request as is, with temporaryPurposeKey provided in the rationale argument. The type is not enforced.
  2. Use check/request, but with an overload specifically for LOCATION_FULL_ACCURACY. I'll call the argument options.
  3. Add a new check/request function for full-accuracy, like notifications.

- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject
rationale:(NSDictionary *_Nullable)rationale {
if (!CLLocationManager.locationServicesEnabled || CLLocationManager.authorizationStatus != RNPermissionStatusAuthorized) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't compare CLLocationManager.authorizationStatus (Apple API) with RNPermissionStatusAuthorized (RNPermissions API)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦 good spotting, not sure what I was thinking.

RNPermissionStatus status = [RNPermissionHandlerLocationFullAccuracy getAccuracyStatus:locationManager];

// Ignore errors due to full accuracy already being authorized
if (error && (error.code != kCLErrorPromptDeclined || status != RNPermissionStatusAuthorized)) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it (error && error.code != kCLErrorPromptDeclined) || status != RNPermissionStatusAuthorized ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, a failure to get authorisation should still resolve the promise successfully.

What's happening here is that requestTemporaryFullAccuracyAuthorizationWithPurposeKey will error with kCLErrorPromptDeclined when permission is already granted, causing it not to show a prompt to the user. When we see this error, and can confirm that authorisation is granted, the call is treated as a success instead.

@zoontek
Copy link
Owner

zoontek commented Sep 3, 2020

@zoontek I'd like to get this in, but if you have reservations about the API then I'm happy to make any suggested changes. I think these are the choices:

  1. Use check/request as is, with temporaryPurposeKey provided in the rationale argument. The type is not enforced.
  2. Use check/request, but with an overload specifically for LOCATION_FULL_ACCURACY. I'll call the argument options.
  3. Add a new check/request function for full-accuracy, like notifications.

A fourth option is possible: enforcing full-accuracy as temporary purpose key for everyone. It's slightly more restrictive (one message for full accuracy permission request per app), but it introduces no changes on the JS API.

Later (when iOS 14 will be widely available), it will be nice to rethink the API in a major react-native-permissions which handle correctly all the new permissions flow for the platform (#508).

@jaspermeijaard
Copy link

Keep up the good work. I think you guys are best aware of the requirements but defining a rest type based on the permission might allow you to make the second argument required/optional? See this example.

@adapptor-kurt
Copy link
Contributor Author

A fourth option is possible: enforcing full-accuracy as temporary purpose key for everyone. It's slightly more restrictive (one message for full accuracy permission request per app), but it introduces no changes on the JS API.

I don't mind adding a default purpose key, but I already have a use case in an app I'm working on for more than one message.

I think you guys are best aware of the requirements but defining a rest type based on the permission might allow you to make the second argument required/optional? See this example.

Ah, I forgot that conditional types are distributive, so this does the right thing 👍

@adapptor-kurt
Copy link
Contributor Author

I have some changes which I think provide a reasonable level of type safety without making things too complicated. I'll use "full-accuracy" as the default purpose key so that the options are no longer required, and then change the definition to this:

function request<T extends Permission = Permission>(
  permission: T,
  options?: T extends typeof PERMISSIONS.IOS.LOCATION_FULL_ACCURACY
    ? FullAccuracyOptionsIOS
    : Rationale,
): Promise<PermissionStatus>;

How's this look?

@adapptor-kurt
Copy link
Contributor Author

@zoontek the code changes are up, just waiting for some feedback before updating documentation.

@jaspermeijaard
Copy link

This API looks good and doesn't seem to break current permission requests. I'll continue testing when it's more final.

@27leaves
Copy link

Do you have a suggestion on how to reference it in an existing project? I tried to modify the package.jslike "react-native-permissions": "react-native-community/react-native-permissions#503/head", but it does not work like this because the lib files are not included. I really need it in an existing project this week and it would be awesome to try and give feedback 👍

@jaspermeijaard
Copy link

Do you have a suggestion on how to reference it in an existing project? I tried to modify the package.jslike "react-native-permissions": "react-native-community/react-native-permissions#503/head", but it does not work like this because the lib files are not included. I really need it in an existing project this week and it would be awesome to try and give feedback 👍

Try using the branch directly from adapptor-kurt his fork, see NPM docs.

@toshe
Copy link

toshe commented Sep 15, 2020

@creat-or you can try this as suggested by @jaspermeijaard

"react-native-permissions": "https://github.com/adapptor-kurt/react-native-permissions#568f153"

@jaspermeijaard
Copy link

@adapptor-kurt When I check this new permission in an iOS 14.0 simulator of Xcode version 12.0 beta 6, regardless of the value of "Precise Location" in the Settings the check always resolves to "unavailable". I should be able to check the permission first before requesting it right? Example:

await Permissions.check(Permissions.PERMISSIONS.IOS.LOCATION_FULL_ACCURACY) // => "unavailable" 

@27leaves
Copy link

Thank you @jaspermeijaard and @toshe!

@jaspermeijaard Yes, I have the same behaviour and also wondering if we miss something.

@jaspermeijaard
Copy link

jaspermeijaard commented Sep 15, 2020

I think status unavailable makes sense when you don't enable the pod 😅

@creat-or You need to include the pod in your project, please review your Podfile and Info.plist see documentation.

@27leaves
Copy link

@jaspermeijaard 🙈 ... awesome, that worked, thank you! :)

@ezeferex
Copy link

@jaspermeijaard @creat-or Hi! I pulled down from that branch into my project and included the pod, but when I tried to compile shows me the next error. Are you getting the same one?

❌  /Users/ezequiel/Projects/ReactNative/mobile/node_modules/react-native-permissions/ios/LocationFullAccuracy/RNPermissionHandlerLocationFullAccuracy.m:28:27: property 'accuracyAuthorization' not found on object of type 'CLLocationManager *'
  switch (locationManager.accuracyAuthorization) {
                          ^
❌  /Users/ezequiel/Projects/ReactNative/mobile/node_modules/react-native-permissions/ios/LocationFullAccuracy/RNPermissionHandlerLocationFullAccuracy.m:71:35: use of undeclared identifier 'kCLErrorPromptDeclined'
      if (error && (error.code != kCLErrorPromptDeclined || status != RNPermissionStatusAuthorized)) {
                                  ^
❌  /Users/ezequiel/Projects/ReactNative/mobile/node_modules/react-native-permissions/ios/LocationFullAccuracy/RNPermissionHandlerLocationFullAccuracy.m:66:22: no visible @interface for 'CLLocationManager' declares the selector 'requestTemporaryFullAccuracyAuthorizationWithPurposeKey:completion:'
    [locationManager requestTemporaryFullAccuracyAuthorizationWithPurposeKey:purposeKey
     ~~~~~~~~~~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

@adapptor-kurt
Copy link
Contributor Author

@ezeferex the LocationFullAccuracy pod requires Xcode 12. The GM seed is available from Apple's developer portal.

For everyone that's tried it, has it all been working ok? The documentation isn't particularly helpful so I could improve that, but most of these permissions already require you to read a lot of external documentation to understand how to use them.

@jaspermeijaard
Copy link

@ezeferex the LocationFullAccuracy pod requires Xcode 12. The GM seed is available from Apple's developer portal.

For everyone that's tried it, has it all been working ok? The documentation isn't particularly helpful so I could improve that, but most of these permissions already require you to read a lot of external documentation to understand how to use them.

@adapptor-kurt Tested on several OS versions with several people and everything seem to work good. Thank you! As far as I am concerned, green light for merging 👍

…curacy

# Conflicts:
#	example/ios/Podfile.lock
@tanishkbajaj
Copy link

By when do you think the code will get merged and permissions library would be ready for use with IOS 14 changes?

@zoontek
Copy link
Owner

zoontek commented Sep 24, 2020

@tanishkbajaj Frankly, I don't know. I planned to work on it all day this Saturday, put cannot promise it will be done on time. A lot of things has to be treated: Checking for support from iOS 10 to iOS 14, add support for Android 11 too (and check for support from Android 4.1 to Android 11!), ensure that we don't miss new features that could introduces another required breaking changes later, check that the APIs for all these new features / permissions play well together, write a migration guide, etc.

I currently work alone on this, do it on my spare time and for free: even if a lot of companies seems to use this package, I got something like 20$ a month (from a personal friend and my company 😅).

Understand me: I don't do OSS for money or glory and I'm not a company with a clear roadmap trying to sell you some service too. That's what open source is 🙂

Sure, I will do my best releasing a beta for this new version as soon as possible. But meanwhile, you can still checkout @adapptor-kurt branch, use https://github.com/ds300/patch-package or even copy and paste new permission handlers directly in your project.

@mikehardy
Copy link

@tanishkbajaj behold the beauty of patch-package! https://github.com/ds300/patch-package

Integrate the PR, and enjoy

@tanishkbajaj
Copy link

@tanishkbajaj Frankly, I don't know. I planned to work on it all day this Saturday, put cannot promise it will be done on time. A lot of things has to be treated: Checking for support from iOS 10 to iOS 14, add support for Android 11 too (and check for support from Android 4.1 to Android 11!), ensure that we don't miss new features that could introduces another required breaking changes later, check that the APIs for all these new features / permissions play well together, write a migration guide, etc.

I currently work alone on this, do it on my spare time and for free: even if a lot of companies seems to use this package, I got something like 20$ a month (from a personal friend and my company 😅).

Understand me: I don't do OSS for money or glory and I'm not a company with a clear roadmap trying to sell you some service too. That's what open source is 🙂

Sure, I will do my best releasing a beta for this new version as soon as possible. But meanwhile, you can still checkout @adapptor-kurt branch, use https://github.com/ds300/patch-package or even copy and paste new permission handlers directly in your project.

@zoontek I understand your point. Hats off to the hard work you guys put, hope I can contribute too next time. Also meanwhile I can checkout and look at @adapptor-kurt branch.

@zoontek zoontek mentioned this pull request Sep 26, 2020
11 tasks
@27leaves
Copy link

27leaves commented Sep 28, 2020

I have a problem now with another permission while using react-native-camera. When I use this PR with https://github.com/adapptor-kurt/react-native-permissions#13bceb138505454890e3f88e516622c6736d3d93, I get the following error when trying to get CAMERA access:

RNPermissions.request was called with 1 arguments but expects 2 arguments. If you haven't changed this method yourself, this usually means that your versions of the native code and JavaScript code are out of sync. Updating both should make this error go away.

With the latest stable version the camera works as expected.
Am I missing something?

@adapptor-kurt
Copy link
Contributor Author

I can't reproduce this error in the react-native-permissions example app. When options is undefined this is passed as a nil argument to native code.

@creat-or Could you provide some more details about what you're testing with (RN, OS, etc versions). Have you tried a clean build, maybe by also reinstalling your node_modules and pods?

@27leaves
Copy link

27leaves commented Sep 29, 2020

I can't reproduce this error in the react-native-permissions example app. When options is undefined this is passed as a nil argument to native code.

@creat-or Could you provide some more details about what you're testing with (RN, OS, etc versions). Have you tried a clean build, maybe by also reinstalling your node_modules and pods?

I managed to reproduce the behavior in a minimal example with the plugins that I use:

https://github.com/creat-or/MinimalQRCodeScanner

The versions, testing on a real iPhone7 with iOS14 (mac OS 10.15.6, XCode 12)

"react": "16.13.1",
    "react-native": "0.63.2",
    "react-native-camera": "^3.40.0",
    "react-native-permissions": "https://github.com/adapptor-kurt/react-native-permissions#13bceb138505454890e3f88e516622c6736d3d93",
    "react-native-qrcode-scanner": "moaazsidat/react-native-qrcode-scanner#v1.5.0"

@adapptor-kurt
Copy link
Contributor Author

adapptor-kurt commented Sep 29, 2020

@creat-or your crash is caused by having two copies of react-native-permissions. Yarn is failing to realise that my fork is compatible with the one used by react-native-qrcode-scanner:

$ find . -name react-native-permissions
./node_modules/react-native-permissions
./node_modules/react-native-qrcode-scanner/node_modules/react-native-permissions

When react-native-qrcode-scanner tries to call the API, it's calling the old API with a single parameter but it ends up invoking the pod from the fork that expects two. You can fix this by forcing the resolution in package.json:

"resolutions": {
  "react-native-qrcode-scanner/react-native-permissions": "https://github.com/adapptor-kurt/react-native-permissions#13bceb138505454890e3f88e516622c6736d3d93"
},

If this PR is merged and released with a compatible minor version, then yarn should resolve this properly and the forced resolution shouldn't be necessary.

@27leaves
Copy link

Thank you very much for your great assistance! Now the camera permissions work again 👍 Thanks!!

@flochtililoch
Copy link

Hi,
any timeframe for this PR?

In case you haven't, see this comment: #503 (comment)

@zoontek zoontek changed the base branch from master to 3.0.0 October 10, 2020 12:51
@zoontek zoontek merged commit ba3b788 into zoontek:3.0.0 Oct 10, 2020
@zoontek
Copy link
Owner

zoontek commented Oct 10, 2020

@adapptor-kurt Sorry for the delay and thanks for the good work.
I'm merging this in the upcoming 3.0.0 branch to continue working on it.

@adapptor-kurt
Copy link
Contributor Author

Thanks @zoontek! Apple and Google don't make it easy, so your hard work putting everything together and testing is really appreciated.

@creat-or since this is going into a v3.0.0, that indicates a breaking change with v2. I would recommend continue using my branch until v3.0.0 is released and react-native-qrcode-scanner is also updated to use it.

@cdavie-weconnect
Copy link

Thanks for this update! @zoontek: Do you have a rough guess of how long before the 3.0.0 branch might be released? Like, do you expect it to be days, weeks, or months? My team is trying to decide whether to just wait for the official release vs using a branch or fork sooner. Thanks again!

@zoontek
Copy link
Owner

zoontek commented Nov 6, 2020

For those who missed it, I published a first beta of the next major release. Feedbacks are welcomed 🙂

yarn add react-native-permissions@next

A migration guide is available here: https://github.com/zoontek/react-native-permissions/blob/3.0.0/MIGRATION.md

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

Successfully merging this pull request may close these issues.

10 participants