expo-location 18.0.9 → 18.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/android/build.gradle +2 -2
- package/ios/Requesters/EXBackgroundLocationPermissionRequester.m +65 -7
- package/ios/Requesters/EXBaseLocationRequester.h +2 -3
- package/ios/Requesters/EXBaseLocationRequester.m +43 -45
- package/ios/TaskConsumers/EXLocationTaskConsumer.m +16 -15
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 18.0.10 — 2025-04-01
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- [iOS] Fixed issue with some permission request flows resolving too soon on iOS. ([#35693](https://github.com/expo/expo/pull/35693) by [@chrfalch](https://github.com/chrfalch))
|
|
18
|
+
- [iOS] Remove restarting all services when CLLocationManager reports an error ([#35478](https://github.com/expo/expo/pull/35478) by [@chrfalch](https://github.com/chrfalch))
|
|
19
|
+
|
|
13
20
|
## 18.0.9 — 2025-03-31
|
|
14
21
|
|
|
15
22
|
_This version does not introduce any user-facing changes._
|
|
@@ -31,6 +38,7 @@ _This version does not introduce any user-facing changes._
|
|
|
31
38
|
|
|
32
39
|
### 🐛 Bug fixes
|
|
33
40
|
|
|
41
|
+
- [iOS] Added guards to avoid task options to crash the app. ([#35477](https://github.com/expo/expo/pull/35477) by [@chrfalch](https://github.com/chrfalch))
|
|
34
42
|
- [iOS] Added error handler to the streaming location/heading methods since these can fail while streaming ([#35004](https://github.com/expo/expo/pull/35004) by [@chrfalch](https://github.com/chrfalch))
|
|
35
43
|
|
|
36
44
|
## 18.0.6 — 2025-02-10
|
package/android/build.gradle
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
2
|
|
|
3
3
|
group = 'host.exp.exponent'
|
|
4
|
-
version = '18.0.
|
|
4
|
+
version = '18.0.10'
|
|
5
5
|
|
|
6
6
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
7
|
apply from: expoModulesCorePlugin
|
|
@@ -14,7 +14,7 @@ android {
|
|
|
14
14
|
namespace "expo.modules.location"
|
|
15
15
|
defaultConfig {
|
|
16
16
|
versionCode 29
|
|
17
|
-
versionName "18.0.
|
|
17
|
+
versionName "18.0.10"
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -10,6 +10,7 @@ static SEL alwaysAuthorizationSelector;
|
|
|
10
10
|
@interface EXBackgroundLocationPermissionRequester ()
|
|
11
11
|
|
|
12
12
|
@property (nonatomic, assign) bool wasAsked;
|
|
13
|
+
@property (nonatomic, assign) bool isWaitingForTimeout;
|
|
13
14
|
|
|
14
15
|
@end
|
|
15
16
|
|
|
@@ -19,6 +20,7 @@ static SEL alwaysAuthorizationSelector;
|
|
|
19
20
|
{
|
|
20
21
|
if (self = [super init]) {
|
|
21
22
|
_wasAsked = false;
|
|
23
|
+
_isWaitingForTimeout = false;
|
|
22
24
|
}
|
|
23
25
|
return self;
|
|
24
26
|
}
|
|
@@ -37,10 +39,28 @@ static SEL alwaysAuthorizationSelector;
|
|
|
37
39
|
{
|
|
38
40
|
if ([EXBaseLocationRequester isConfiguredForAlwaysAuthorization] && [self.locationManager respondsToSelector:alwaysAuthorizationSelector]) {
|
|
39
41
|
_wasAsked = true;
|
|
40
|
-
[
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
CLAuthorizationStatus status = [self.locationManager authorizationStatus];
|
|
43
|
+
|
|
44
|
+
if (status == kCLAuthorizationStatusAuthorizedWhenInUse) {
|
|
45
|
+
// We already have a foreground permission granted:
|
|
46
|
+
// When asking for background location, we might or might not have asked for foreground permission
|
|
47
|
+
// before we get here. An issue here is if the user has a temporary permission ("Allow once") - which
|
|
48
|
+
// results in the status being "kCLAuthorizationStatusAuthorizedWhenInUse" - without us knowing.
|
|
49
|
+
// We need to handle this special case which is not possible to detect through the API.
|
|
50
|
+
// What we do is that we'll wait 1.5 seconds on an UIApplicationWillResignActiveNotification
|
|
51
|
+
// notification (which will be emitted almost directly if the permission dialog is displayed). If the permission
|
|
52
|
+
// dialog is not displayed we'll timeout and can resolve the waiting promise with an updated denied status.
|
|
53
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
54
|
+
selector:@selector(handleAppBecomingInactive)
|
|
55
|
+
name:UIApplicationWillResignActiveNotification
|
|
56
|
+
object:nil];
|
|
57
|
+
|
|
58
|
+
// Setup timeout - if no permission dialog was displayed we can just stop listening and deny
|
|
59
|
+
// the request
|
|
60
|
+
[self setupAppInactivateTimeout];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Request permissions
|
|
44
64
|
((void (*)(id, SEL))objc_msgSend)(self.locationManager, alwaysAuthorizationSelector);
|
|
45
65
|
} else {
|
|
46
66
|
self.reject(@"ERR_LOCATION_INFO_PLIST", @"One of the `NSLocation*UsageDescription` keys must be present in Info.plist to be able to use geolocation.", nil);
|
|
@@ -50,9 +70,6 @@ static SEL alwaysAuthorizationSelector;
|
|
|
50
70
|
}
|
|
51
71
|
}
|
|
52
72
|
|
|
53
|
-
// If user selects "Keep Only While Using" option, the `locationManagerDidChangeAuthorization` won't be called.
|
|
54
|
-
// So we don't know when we should resolve promise.
|
|
55
|
-
// Hovewer, we can check for `UIApplicationDidBecomeActiveNotification` event which is called when permissions modal disappears.
|
|
56
73
|
- (void)handleAppBecomingActive
|
|
57
74
|
{
|
|
58
75
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
@@ -63,6 +80,47 @@ static SEL alwaysAuthorizationSelector;
|
|
|
63
80
|
}
|
|
64
81
|
}
|
|
65
82
|
|
|
83
|
+
- (void)handleAppBecomingInactive
|
|
84
|
+
{
|
|
85
|
+
// Let's wait until the app becomes inactive - this happens when OS displays the
|
|
86
|
+
// permission dialog - then we can cancel the timeout handler.
|
|
87
|
+
|
|
88
|
+
_isWaitingForTimeout = false;
|
|
89
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
90
|
+
|
|
91
|
+
// When the app is inactive it means that a permission dialog is showing and we should ask to be
|
|
92
|
+
// notified when the dialog is closed:
|
|
93
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
94
|
+
selector:@selector(handleAppBecomingActive)
|
|
95
|
+
name:UIApplicationDidBecomeActiveNotification
|
|
96
|
+
object:nil];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
- (void)setupAppInactivateTimeout
|
|
100
|
+
{
|
|
101
|
+
_isWaitingForTimeout = true;
|
|
102
|
+
|
|
103
|
+
// Obtain a reference to the current queue
|
|
104
|
+
dispatch_queue_t currentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
|
|
105
|
+
|
|
106
|
+
// Calculate the time for the delay
|
|
107
|
+
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC));
|
|
108
|
+
|
|
109
|
+
EX_WEAKIFY(self);
|
|
110
|
+
|
|
111
|
+
// Schedule the block to be executed after the delay
|
|
112
|
+
dispatch_after(delayTime, currentQueue, ^{
|
|
113
|
+
EX_ENSURE_STRONGIFY(self)
|
|
114
|
+
// Check if we are still waiting - ie. we haven't seen a permission dialog
|
|
115
|
+
if (self.isWaitingForTimeout && self.resolve) {
|
|
116
|
+
self.isWaitingForTimeout = false;
|
|
117
|
+
self.resolve([self getPermissions]);
|
|
118
|
+
self.resolve = nil;
|
|
119
|
+
self.reject = nil;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
66
124
|
- (NSDictionary *)parsePermissions:(CLAuthorizationStatus)systemStatus
|
|
67
125
|
{
|
|
68
126
|
EXPermissionStatus status;
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
#import <CoreLocation/CLLocationManager.h>
|
|
4
4
|
|
|
5
5
|
#import <ExpoModulesCore/EXPermissionsInterface.h>
|
|
6
|
+
#import <CoreLocation/CLLocationManagerDelegate.h>
|
|
6
7
|
|
|
7
|
-
@interface EXBaseLocationRequester : NSObject<EXPermissionsRequester>
|
|
8
|
+
@interface EXBaseLocationRequester : NSObject<EXPermissionsRequester, CLLocationManagerDelegate>
|
|
8
9
|
|
|
9
10
|
@property (nonatomic, strong) CLLocationManager *locationManager;
|
|
10
11
|
@property (nonatomic, strong) EXPromiseResolveBlock resolve;
|
|
@@ -16,6 +17,4 @@
|
|
|
16
17
|
- (void)requestLocationPermissions;
|
|
17
18
|
- (NSDictionary *)parsePermissions:(CLAuthorizationStatus)systemStatus;
|
|
18
19
|
|
|
19
|
-
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status;
|
|
20
|
-
|
|
21
20
|
@end
|
|
@@ -4,12 +4,11 @@
|
|
|
4
4
|
#import <ExpoModulesCore/EXUtilities.h>
|
|
5
5
|
|
|
6
6
|
#import <objc/message.h>
|
|
7
|
-
#import <CoreLocation/CLLocationManagerDelegate.h>
|
|
8
7
|
|
|
9
8
|
@interface EXBaseLocationRequester () <CLLocationManagerDelegate>
|
|
10
9
|
|
|
11
10
|
@property (nonatomic, assign) bool locationManagerWasCalled;
|
|
12
|
-
|
|
11
|
+
@property (nonatomic, assign) CLAuthorizationStatus beginStatus;
|
|
13
12
|
|
|
14
13
|
@end
|
|
15
14
|
|
|
@@ -73,25 +72,47 @@
|
|
|
73
72
|
// make Expo developers receive this kind of messages nor add our own default usage description,
|
|
74
73
|
// we try to fool the static analyzer and construct the selector in runtime.
|
|
75
74
|
// This way behavior of this requester is governed by provided NSLocationUsageDescriptions.
|
|
75
|
+
|
|
76
|
+
// 2. Location permission request types
|
|
76
77
|
//
|
|
77
|
-
//
|
|
78
|
+
// Foreground
|
|
79
|
+
// - "Allow once"
|
|
80
|
+
// - "Allow while using App"
|
|
81
|
+
// - "Don't allow"
|
|
78
82
|
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
83
|
+
// Background
|
|
84
|
+
// - "Keep only while using"
|
|
85
|
+
// - "Change to always allow"
|
|
86
|
+
//
|
|
87
|
+
// Requesting background permissions directly without first asking for foreground permissions is the
|
|
88
|
+
// same as asking for foreground permissions and then asking for background permissions.
|
|
89
|
+
//
|
|
90
|
+
// "Allow once" is a temporary permission (limited to the current app session). It is not possible to get
|
|
91
|
+
// info from the API about wether or not the current permission is temporary. You cannot request background
|
|
92
|
+
// permissions with a temporary token - a background request will then return denied.
|
|
93
|
+
//
|
|
94
|
+
// Requesting background permissions directly and "Allow while using the App" gives you a provisional
|
|
95
|
+
// background permission that can later be elevated to a full "Always allow" permission.
|
|
96
|
+
// You will be asked at a later point if you want to convert to "Always allow". The system waits until
|
|
97
|
+
// you have started using the newly aquired permission before showing the permission dialog.
|
|
98
|
+
//
|
|
99
|
+
// Test the following scenarios in BareExpo -> APIs -> Location
|
|
100
|
+
// ------------------------------------------------------------
|
|
101
|
+
// (before tests, make sure to clear any location permissions and restart the app)
|
|
102
|
+
//
|
|
103
|
+
// rfp = requestForegroundPermissionsAsync, fp: Actual foreground permission given
|
|
104
|
+
// rbp = requestBackgroundPermissionsAsync, bg: Actual background permission given
|
|
105
|
+
//
|
|
106
|
+
// - rfp -> "Allow once", then rbp -> no dialog = (fp: granted (temporary), bg: denied after 1.5 seconds)
|
|
107
|
+
// - rfp -> "Allow while using App", then rbp -> "Keep only while using" = (fp: granted, bg: denied)
|
|
108
|
+
// - rfp -> "Allow while using App", then rbp -> "Change to always allow" = (fp: granted, bg: granted)
|
|
109
|
+
// - rfp -> "Don't allow", then rbp -> no dialog = (fp: denied, bg: denied)
|
|
110
|
+
// - rbp -> "Allow once", no more dalogs = (fp: granted (temporary), bg: denied)
|
|
111
|
+
// - rbp -> "Allow while using App", no more dialogs = (fp: granted, bg: granted (provisional))
|
|
112
|
+
// - rbp -> "Don't allow" = (fp: denied, bg: denied)
|
|
113
|
+
|
|
114
|
+
// Save start statue and call requestLocationPermissions
|
|
115
|
+
_beginStatus = [self.locationManager authorizationStatus];
|
|
95
116
|
[self requestLocationPermissions];
|
|
96
117
|
}
|
|
97
118
|
}
|
|
@@ -107,38 +128,15 @@
|
|
|
107
128
|
}
|
|
108
129
|
}
|
|
109
130
|
|
|
110
|
-
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
|
|
111
|
-
{
|
|
112
|
-
// TODO: Permissions.LOCATION issue (search by this phrase)
|
|
113
|
-
// if Permissions.LOCATION is being called for the first time on iOS devide and prompts for user action it might not call this callback at all
|
|
114
|
-
// it happens if user requests more that one permission at the same time via Permissions.askAsync(...) and LOCATION dialog is not being called first
|
|
115
|
-
// to reproduce this find NCL code testing that
|
|
116
|
-
if (status == kCLAuthorizationStatusNotDetermined || !_locationManagerWasCalled) {
|
|
117
|
-
// CLLocationManager calls this delegate method once on start with kCLAuthorizationNotDetermined even before the user responds
|
|
118
|
-
// to the "Don't Allow" / "Allow" dialog box. This isn't the event we care about so we skip it. See:
|
|
119
|
-
// http://stackoverflow.com/questions/30106341/swift-locationmanager-didchangeauthorizationstatus-always-called/30107511#30107511
|
|
120
|
-
_locationManagerWasCalled = true;
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (_resolve) {
|
|
125
|
-
_resolve([self getPermissions]);
|
|
126
|
-
_resolve = nil;
|
|
127
|
-
_reject = nil;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
131
|
- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager
|
|
132
132
|
{
|
|
133
|
-
CLAuthorizationStatus
|
|
134
|
-
if (
|
|
133
|
+
CLAuthorizationStatus nextState = [manager authorizationStatus];
|
|
134
|
+
if (_beginStatus == nextState && !_locationManagerWasCalled) {
|
|
135
135
|
// CLLocationManager calls this delegate method once on start with kCLAuthorizationNotDetermined even before the user responds
|
|
136
136
|
// to the "Don't Allow" / "Allow" dialog box. This isn't the event we care about so we skip it. See:
|
|
137
137
|
// http://stackoverflow.com/questions/30106341/swift-locationmanager-didchangeauthorizationstatus-always-called/30107511#30107511
|
|
138
138
|
_locationManagerWasCalled = true;
|
|
139
|
-
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
139
|
+
return;
|
|
142
140
|
}
|
|
143
141
|
|
|
144
142
|
if (_resolve) {
|
|
@@ -68,11 +68,10 @@
|
|
|
68
68
|
EXLocationAccuracy accuracy = [options[@"accuracy"] unsignedIntegerValue] ?: EXLocationAccuracyBalanced;
|
|
69
69
|
|
|
70
70
|
locationManager.desiredAccuracy = [EXLocation CLLocationAccuracyFromOption:accuracy];
|
|
71
|
-
locationManager.distanceFilter = [options[@"distanceInterval"]
|
|
72
|
-
locationManager.activityType = [EXLocation CLActivityTypeFromOption:[options[@"activityType"]
|
|
73
|
-
locationManager.pausesLocationUpdatesAutomatically = [options[@"pausesUpdatesAutomatically"]
|
|
74
|
-
|
|
75
|
-
locationManager.showsBackgroundLocationIndicator = [options[@"showsBackgroundLocationIndicator"] boolValue];
|
|
71
|
+
locationManager.distanceFilter = [self numberToDouble:options[@"distanceInterval"] defaultValue:kCLDistanceFilterNone];
|
|
72
|
+
locationManager.activityType = [EXLocation CLActivityTypeFromOption:[self numberToInteger:options[@"activityType"] defaultValue:CLActivityTypeOther]];
|
|
73
|
+
locationManager.pausesLocationUpdatesAutomatically = [self numberToBool:options[@"pausesUpdatesAutomatically"] defaultValue:true];
|
|
74
|
+
locationManager.showsBackgroundLocationIndicator = [self numberToBool:options[@"showsBackgroundLocationIndicator"] defaultValue:false];
|
|
76
75
|
|
|
77
76
|
[locationManager startUpdatingLocation];
|
|
78
77
|
[locationManager startMonitoringSignificantLocationChanges];
|
|
@@ -91,15 +90,7 @@
|
|
|
91
90
|
|
|
92
91
|
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
|
|
93
92
|
{
|
|
94
|
-
|
|
95
|
-
// This error might happen when the device is not able to find out the location. Try to restart monitoring location.
|
|
96
|
-
[manager stopUpdatingLocation];
|
|
97
|
-
[manager stopMonitoringSignificantLocationChanges];
|
|
98
|
-
[manager startUpdatingLocation];
|
|
99
|
-
[manager startMonitoringSignificantLocationChanges];
|
|
100
|
-
} else {
|
|
101
|
-
[_task executeWithData:nil withError:error];
|
|
102
|
-
}
|
|
93
|
+
[_task executeWithData:nil withError:error];
|
|
103
94
|
}
|
|
104
95
|
|
|
105
96
|
# pragma mark - internal
|
|
@@ -172,7 +163,17 @@
|
|
|
172
163
|
|
|
173
164
|
- (double)numberToDouble:(NSNumber *)number defaultValue:(double)defaultValue
|
|
174
165
|
{
|
|
175
|
-
return number == nil ? defaultValue : [number doubleValue];
|
|
166
|
+
return [number isEqual:[NSNull null]] || number == nil ? defaultValue : [number doubleValue];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
- (NSInteger)numberToInteger:(NSNumber *)number defaultValue:(NSInteger)defaultValue
|
|
170
|
+
{
|
|
171
|
+
return [number isEqual:[NSNull null]] || number == nil ? defaultValue : [number integerValue];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
- (BOOL)numberToBool:(NSNumber *)number defaultValue:(BOOL)defaultValue
|
|
175
|
+
{
|
|
176
|
+
return [number isEqual:[NSNull null]] || number == nil ? defaultValue : [number boolValue];
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
+ (NSArray<NSDictionary *> *)_exportLocations:(NSArray<CLLocation *> *)locations
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-location",
|
|
3
|
-
"version": "18.0.
|
|
3
|
+
"version": "18.0.10",
|
|
4
4
|
"description": "Allows reading geolocation information from the device. Your app can poll for the current location or subscribe to location update events.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"expo": "*"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "b08a0bd52965f85871c12c31da16a45e2cd26c4c"
|
|
48
48
|
}
|