Skip to content

[iOS] Added permission getter and listener #249

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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ When promise resolves, returns the status of the authorization.
- `denied` - Permission denied
- `restricted` - Permission restricted

#### `async getCurrentAuthorization() (iOS only)`
Get the current location permission status for the app without prompting the user for it.
Possible return values are the same as for `requestAuthorization` and `null` can also be returned if no specific permission has been granted.

#### `watchPermission(changedCallback) (iOS Only)`

Subscribe to changes to the location permission. These can happen if the user changes them in the settings app while the app is running. `changedCallback` will be called with the authorization result (return values of `requestAuthorization`).

**Returns**: A function to unsubscribe (remove the listener). If called `changedCallback` will stop receiving permission updates.

#### `getCurrentPosition(successCallback, ?errorCallback, ?options)`
- **successCallback**: Invoked with latest location info.
- **errorCallback**: Invoked whenever an error is encountered.
Expand Down
8 changes: 8 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,14 @@ declare module 'react-native-geolocation-service' {
mocked?: boolean;
}

type PermissionChangedCallback = (permission: AuthorizationResult) => void

type SuccessCallback = (position: GeoPosition) => void

type ErrorCallback = (error: GeoError) => void

export function getCurrentAuthorization(): Promise<AuthorizationResult | null>

export function requestAuthorization(
authorizationLevel: AuthorizationLevel
): Promise<AuthorizationResult>
Expand All @@ -85,6 +89,10 @@ declare module 'react-native-geolocation-service' {
options?: GeoOptions
): void

export function watchPermission(
changedCallback: PermissionChangedCallback
): () => void

export function watchPosition(
successCallback: SuccessCallback,
errorCallback?: ErrorCallback,
Expand Down
78 changes: 52 additions & 26 deletions ios/RNFusedLocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class RNFusedLocation: RCTEventEmitter {
private var resolveAuthorizationStatus: RCTPromiseResolveBlock? = nil
private var successCallback: RCTResponseSenderBlock? = nil
private var errorCallback: RCTResponseSenderBlock? = nil
private var permissionListenerCount: Int = 0

override init() {
super.init()
Expand All @@ -43,6 +44,15 @@ class RNFusedLocation: RCTEventEmitter {

locationManager.delegate = nil;
}

// MARK: Bridge Method
@objc(getCurrentAuthorization:reject:)
func getCurrentAuthorization(
resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) -> Void {
resolve(getCurrentAuthorizationStatus())
}

// MARK: Bridge Method
@objc func requestAuthorization(
Expand All @@ -56,19 +66,10 @@ class RNFusedLocation: RCTEventEmitter {
resolve(AuthorizationStatus.disabled.rawValue)
return
}

switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse, .authorizedAlways:
resolve(AuthorizationStatus.granted.rawValue)
return
case .denied:
resolve(AuthorizationStatus.denied.rawValue)
return
case .restricted:
resolve(AuthorizationStatus.restricted.rawValue)
return
default:
break

if let currentStatus = getCurrentAuthorizationStatus() {
resolve(currentStatus)
return
}

resolveAuthorizationStatus = resolve
Expand Down Expand Up @@ -153,6 +154,16 @@ class RNFusedLocation: RCTEventEmitter {

observing = false
}

// MARK: Bridge Method
@objc func permissionListenerAdded() -> Void {
permissionListenerCount += 1
}

// MARK: Bridge Method
@objc func permissionListenerRemoved() -> Void {
permissionListenerCount -= 1
}

@objc func timerFired(timer: Timer) -> Void {
let data = timer.userInfo as! [String: Any]
Expand Down Expand Up @@ -231,6 +242,23 @@ class RNFusedLocation: RCTEventEmitter {

return false
}

private func getAuthorizationStatusString(_ authorizationStatus: CLAuthorizationStatus) -> String? {
switch authorizationStatus {
case .authorizedWhenInUse, .authorizedAlways:
return AuthorizationStatus.granted.rawValue
case .denied:
return AuthorizationStatus.denied.rawValue
case .restricted:
return AuthorizationStatus.restricted.rawValue
default:
return nil
}
}

private func getCurrentAuthorizationStatus() -> String? {
return getAuthorizationStatusString(CLLocationManager.authorizationStatus())
}

private func generateErrorResponse(code: Int, message: String = "") -> [String: Any] {
var msg: String = message
Expand Down Expand Up @@ -268,7 +296,7 @@ extension RNFusedLocation {
}

override func supportedEvents() -> [String]! {
return ["geolocationDidChange", "geolocationError"]
return ["geolocationDidChange", "geolocationError", "geolocationPermissionDidChange"]
}

override func startObserving() -> Void {
Expand All @@ -282,22 +310,20 @@ extension RNFusedLocation {

extension RNFusedLocation: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .notDetermined || resolveAuthorizationStatus == nil {
if status == .notDetermined {
return
}

let statusString = getAuthorizationStatusString(status)

switch status {
case .authorizedWhenInUse, .authorizedAlways:
resolveAuthorizationStatus?(AuthorizationStatus.granted.rawValue)
case .denied:
resolveAuthorizationStatus?(AuthorizationStatus.denied.rawValue)
case .restricted:
resolveAuthorizationStatus?(AuthorizationStatus.restricted.rawValue)
default:
break
if let resolve = resolveAuthorizationStatus {
resolve(statusString)
resolveAuthorizationStatus = nil
}

if (permissionListenerCount > 0) {
sendEvent(withName: "geolocationPermissionDidChange", body: statusString)
}

resolveAuthorizationStatus = nil
}

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
Expand Down
9 changes: 9 additions & 0 deletions ios/RNFusedLocationBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@

@interface RCT_EXTERN_MODULE(RNFusedLocation, RCTEventEmitter)

RCT_EXTERN_METHOD(
getCurrentAuthorization:(RCTPromiseResolveBlock *)resolve
reject:(RCTPromiseRejectBlock)reject
)

RCT_EXTERN_METHOD(
requestAuthorization:(NSString *)level
resolve:(RCTPromiseResolveBlock)resolve
Expand All @@ -15,6 +20,10 @@ @interface RCT_EXTERN_MODULE(RNFusedLocation, RCTEventEmitter)
errorCallback:(RCTResponseSenderBlock)errorCallback
)

RCT_EXTERN_METHOD(permissionListenerAdded)

RCT_EXTERN_METHOD(permissionListenerRemoved)

_RCT_EXTERN_REMAP_METHOD(
startObserving,
startLocationUpdate:(NSDictionary *)options,
Expand Down
8 changes: 8 additions & 0 deletions js/Geolocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ const Geolocation = {
throw new Error('Method not supported by browser');
},

getCurrentAuthorization: function () {
throw new Error('Method not supported by browser');
},

requestAuthorization: async function () {
return Promise.reject('Method not supported by browser');
},
Expand All @@ -20,6 +24,10 @@ const Geolocation = {
navigator.geolocation.getCurrentPosition(success, error, options);
},

watchPermission: function () {
throw new Error('Method not supported by browser')
},

watchPosition: function (success, error, options) {
if (!success) {
throw new Error('Must provide a success callback');
Expand Down
24 changes: 24 additions & 0 deletions js/Geolocation.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ let updatesEnabled = false;
const Geolocation = {
setRNConfiguration: (config) => {}, // eslint-disable-line no-unused-vars

getCurrentAuthorization: () => {
if (Platform.OS !== 'ios') {
return Promise.reject('getCurrentAuthorization is only for iOS');
}

return RNFusedLocation.getCurrentAuthorization();
},

requestAuthorization: async (authorizationLevel) => {
if (Platform.OS !== 'ios') {
return Promise.reject('requestAuthorization is only for iOS');
Expand All @@ -33,6 +41,22 @@ const Geolocation = {
RNFusedLocation.getCurrentPosition(options, success, error);
},

watchPermission: (changed) => {
if (Platform.OS !== 'ios') {
throw new Error('watchPermission is only for iOS');
}
if (!changed) {
throw new Error('Must provide a changed callback');
}

const subscription = LocationEventEmitter.addListener('geolocationPermissionDidChange', changed);
RNFusedLocation.permissionListenerAdded();
return () => {
RNFusedLocation.permissionListenerRemoved();
subscription.remove();
};
},

watchPosition: (success, error = null, options = {}) => {
if (!success) {
// eslint-disable-next-line no-console
Expand Down