1import { 2 PermissionStatus, 3 PermissionResponse, 4 PermissionHookOptions, 5 createPermissionHook, 6 Platform, 7} from 'expo-modules-core'; 8 9import ExpoLocation from './ExpoLocation'; 10import { 11 LocationAccuracy, 12 LocationCallback, 13 LocationGeocodedAddress, 14 LocationGeocodedLocation, 15 LocationHeadingCallback, 16 LocationHeadingObject, 17 LocationLastKnownOptions, 18 LocationObject, 19 LocationOptions, 20 LocationPermissionResponse, 21 LocationProviderStatus, 22 LocationRegion, 23 LocationSubscription, 24 LocationTaskOptions, 25 LocationActivityType, 26 LocationGeofencingEventType, 27 LocationGeofencingRegionState, 28 LocationGeocodingOptions, 29} from './Location.types'; 30import { LocationEventEmitter } from './LocationEventEmitter'; 31import { LocationSubscriber, HeadingSubscriber, _getCurrentWatchId } from './LocationSubscribers'; 32 33// @needsAudit 34/** 35 * @deprecated The Geocoding web api is no longer available from SDK 49 onwards. Use [Place Autocomplete](https://developers.google.com/maps/documentation/places/web-service/autocomplete) instead. 36 * @param apiKey Google API key obtained from Google API Console. This API key must have `Geocoding API` 37 * enabled, otherwise your geocoding requests will be denied. 38 */ 39function setGoogleApiKey(_apiKey: string) {} 40 41// @needsAudit 42/** 43 * Check status of location providers. 44 * @return A promise which fulfills with an object of type [LocationProviderStatus](#locationproviderstatus). 45 */ 46export async function getProviderStatusAsync(): Promise<LocationProviderStatus> { 47 return ExpoLocation.getProviderStatusAsync(); 48} 49 50// @needsAudit 51/** 52 * Asks the user to turn on high accuracy location mode which enables network provider that uses 53 * Google Play services to improve location accuracy and location-based services. 54 * @return A promise resolving as soon as the user accepts the dialog. Rejects if denied. 55 */ 56export async function enableNetworkProviderAsync(): Promise<void> { 57 // If network provider is disabled (user's location mode is set to "Device only"), 58 // Android's location provider may not give you any results. Use this method in order to ask the user 59 // to change the location mode to "High accuracy" which uses Google Play services and enables network provider. 60 // `getCurrentPositionAsync` and `watchPositionAsync` are doing it automatically anyway. 61 62 if (Platform.OS === 'android') { 63 return ExpoLocation.enableNetworkProviderAsync(); 64 } 65} 66 67// @needsAudit 68/** 69 * Requests for one-time delivery of the user's current location. 70 * Depending on given `accuracy` option it may take some time to resolve, 71 * especially when you're inside a building. 72 * > __Note:__ Calling it causes the location manager to obtain a location fix which may take several 73 * > seconds. Consider using [`Location.getLastKnownPositionAsync`](#locationgetlastknownpositionasyncoptions) 74 * > if you expect to get a quick response and high accuracy is not required. 75 * @param options 76 * @return A promise which fulfills with an object of type [`LocationObject`](#locationobject). 77 */ 78export async function getCurrentPositionAsync( 79 options: LocationOptions = {} 80): Promise<LocationObject> { 81 return ExpoLocation.getCurrentPositionAsync(options); 82} 83 84// @needsAudit 85/** 86 * Gets the last known position of the device or `null` if it's not available or doesn't match given 87 * requirements such as maximum age or required accuracy. 88 * It's considered to be faster than `getCurrentPositionAsync` as it doesn't request for the current 89 * location, but keep in mind the returned location may not be up-to-date. 90 * @param options 91 * @return A promise which fulfills with an object of type [LocationObject](#locationobject) or 92 * `null` if it's not available or doesn't match given requirements such as maximum age or required 93 * accuracy. 94 */ 95export async function getLastKnownPositionAsync( 96 options: LocationLastKnownOptions = {} 97): Promise<LocationObject | null> { 98 return ExpoLocation.getLastKnownPositionAsync(options); 99} 100 101// @needsAudit 102/** 103 * Subscribe to location updates from the device. Please note that updates will only occur while the 104 * application is in the foreground. To get location updates while in background you'll need to use 105 * [Location.startLocationUpdatesAsync](#locationstartlocationupdatesasynctaskname-options). 106 * @param options 107 * @param callback This function is called on each location update. It receives an object of type 108 * [`LocationObject`](#locationobject) as the first argument. 109 * @return A promise which fulfills with a [`LocationSubscription`](#locationsubscription) object. 110 */ 111export async function watchPositionAsync( 112 options: LocationOptions, 113 callback: LocationCallback 114): Promise<LocationSubscription> { 115 const watchId = LocationSubscriber.registerCallback(callback); 116 await ExpoLocation.watchPositionImplAsync(watchId, options); 117 118 return { 119 remove() { 120 LocationSubscriber.unregisterCallback(watchId); 121 }, 122 }; 123} 124 125// @needsAudit 126/** 127 * Gets the current heading information from the device. To simplify, it calls `watchHeadingAsync` 128 * and waits for a couple of updates, and then returns the one that is accurate enough. 129 * @return A promise which fulfills with an object of type [LocationHeadingObject](#locationheadingobject). 130 */ 131export async function getHeadingAsync(): Promise<LocationHeadingObject> { 132 return new Promise(async (resolve) => { 133 let tries = 0; 134 135 const subscription = await watchHeadingAsync((heading) => { 136 if (heading.accuracy > 1 || tries > 5) { 137 subscription.remove(); 138 resolve(heading); 139 } else { 140 tries += 1; 141 } 142 }); 143 }); 144} 145 146// @needsAudit 147/** 148 * Subscribe to compass updates from the device. 149 * @param callback This function is called on each compass update. It receives an object of type 150 * [LocationHeadingObject](#locationheadingobject) as the first argument. 151 * @return A promise which fulfills with a [`LocationSubscription`](#locationsubscription) object. 152 */ 153export async function watchHeadingAsync( 154 callback: LocationHeadingCallback 155): Promise<LocationSubscription> { 156 const watchId = HeadingSubscriber.registerCallback(callback); 157 await ExpoLocation.watchDeviceHeading(watchId); 158 159 return { 160 remove() { 161 HeadingSubscriber.unregisterCallback(watchId); 162 }, 163 }; 164} 165 166// @needsAudit 167/** 168 * Geocode an address string to latitude-longitude location. 169 * > **Note**: Using the Geocoding web api is no longer supported. Use [Place Autocomplete](https://developers.google.com/maps/documentation/places/web-service/autocomplete) instead. 170 * 171 * > **Note**: Geocoding is resource consuming and has to be used reasonably. Creating too many 172 * > requests at a time can result in an error, so they have to be managed properly. 173 * > It's also discouraged to use geocoding while the app is in the background and its results won't 174 * > be shown to the user immediately. 175 * 176 * > On Android, you must request a location permission (`Permissions.LOCATION`) from the user 177 * > before geocoding can be used. 178 * @param address A string representing address, eg. `"Baker Street London"`. 179 * @param options 180 * @return A promise which fulfills with an array (in most cases its size is 1) of [`LocationGeocodedLocation`](#locationgeocodedlocation) objects. 181 */ 182export async function geocodeAsync( 183 address: string, 184 options?: LocationGeocodingOptions 185): Promise<LocationGeocodedLocation[]> { 186 if (typeof address !== 'string') { 187 throw new TypeError(`Address to geocode must be a string. Got ${address} instead.`); 188 } 189 190 if (options?.useGoogleMaps || Platform.OS === 'web') { 191 if (__DEV__) { 192 console.warn( 193 'The Geocoding API has been removed in SDK 49, use Place Autocomplete service instead' + 194 '(https://developers.google.com/maps/documentation/places/web-service/autocomplete)' 195 ); 196 } 197 return []; 198 } 199 200 return await ExpoLocation.geocodeAsync(address); 201} 202 203// @needsAudit 204/** 205 * Reverse geocode a location to postal address. 206 * > **Note**: Using the Geocoding web api is no longer supported. Use [Place Autocomplete](https://developers.google.com/maps/documentation/places/web-service/autocomplete) instead. 207 * 208 * > **Note**: Geocoding is resource consuming and has to be used reasonably. Creating too many 209 * > requests at a time can result in an error, so they have to be managed properly. 210 * > It's also discouraged to use geocoding while the app is in the background and its results won't 211 * > be shown to the user immediately. 212 * 213 * > On Android, you must request a location permission (`Permissions.LOCATION`) from the user 214 * > before geocoding can be used. 215 * @param location An object representing a location. 216 * @param options 217 * @return A promise which fulfills with an array (in most cases its size is 1) of [`LocationGeocodedAddress`](#locationgeocodedaddress) objects. 218 */ 219export async function reverseGeocodeAsync( 220 location: Pick<LocationGeocodedLocation, 'latitude' | 'longitude'>, 221 options?: LocationGeocodingOptions 222): Promise<LocationGeocodedAddress[]> { 223 if (typeof location.latitude !== 'number' || typeof location.longitude !== 'number') { 224 throw new TypeError( 225 'Location to reverse-geocode must be an object with number properties `latitude` and `longitude`.' 226 ); 227 } 228 229 if (options?.useGoogleMaps || Platform.OS === 'web') { 230 if (__DEV__) { 231 console.warn( 232 'The Geocoding API has been removed in SDK 49, use Place Autocomplete service instead' + 233 '(https://developers.google.com/maps/documentation/places/web-service/autocomplete)' 234 ); 235 } 236 return []; 237 } 238 239 return await ExpoLocation.reverseGeocodeAsync(location); 240} 241 242// @needsAudit 243/** 244 * Checks user's permissions for accessing location. 245 * @return A promise that fulfills with an object of type [LocationPermissionResponse](#locationpermissionresponse). 246 * @deprecated Use [`getForegroundPermissionsAsync`](#locationgetforegroundpermissionsasync) or [`getBackgroundPermissionsAsync`](#locationgetbackgroundpermissionsasync) instead. 247 */ 248export async function getPermissionsAsync(): Promise<LocationPermissionResponse> { 249 console.warn( 250 `"getPermissionsAsync()" is now deprecated. Please use "getForegroundPermissionsAsync()" or "getBackgroundPermissionsAsync()" instead.` 251 ); 252 return await ExpoLocation.getPermissionsAsync(); 253} 254 255// @needsAudit 256/** 257 * Asks the user to grant permissions for location. 258 * @return A promise that fulfills with an object of type [LocationPermissionResponse](#locationpermissionresponse). 259 * @deprecated Use [`requestForegroundPermissionsAsync`](#locationrequestforegroundpermissionsasync) or [`requestBackgroundPermissionsAsync`](#locationrequestbackgroundpermissionsasync) instead. 260 */ 261export async function requestPermissionsAsync(): Promise<LocationPermissionResponse> { 262 console.warn( 263 `"requestPermissionsAsync()" is now deprecated. Please use "requestForegroundPermissionsAsync()" or "requestBackgroundPermissionsAsync()" instead.` 264 ); 265 266 return await ExpoLocation.requestPermissionsAsync(); 267} 268 269// @needsAudit 270/** 271 * Checks user's permissions for accessing location while the app is in the foreground. 272 * @return A promise that fulfills with an object of type [PermissionResponse](#permissionresponse). 273 */ 274export async function getForegroundPermissionsAsync(): Promise<LocationPermissionResponse> { 275 return await ExpoLocation.getForegroundPermissionsAsync(); 276} 277 278// @needsAudit 279/** 280 * Asks the user to grant permissions for location while the app is in the foreground. 281 * @return A promise that fulfills with an object of type [PermissionResponse](#permissionresponse). 282 */ 283export async function requestForegroundPermissionsAsync(): Promise<LocationPermissionResponse> { 284 return await ExpoLocation.requestForegroundPermissionsAsync(); 285} 286 287// @needsAudit 288/** 289 * Check or request permissions for the foreground location. 290 * This uses both `requestForegroundPermissionsAsync` and `getForegroundPermissionsAsync` to interact with the permissions. 291 * 292 * @example 293 * ```ts 294 * const [status, requestPermission] = Location.useForegroundPermissions(); 295 * ``` 296 */ 297export const useForegroundPermissions = createPermissionHook({ 298 getMethod: getForegroundPermissionsAsync, 299 requestMethod: requestForegroundPermissionsAsync, 300}); 301 302// @needsAudit 303/** 304 * Checks user's permissions for accessing location while the app is in the background. 305 * @return A promise that fulfills with an object of type [PermissionResponse](#permissionresponse). 306 */ 307export async function getBackgroundPermissionsAsync(): Promise<PermissionResponse> { 308 return await ExpoLocation.getBackgroundPermissionsAsync(); 309} 310 311// @needsAudit 312/** 313 * Asks the user to grant permissions for location while the app is in the background. 314 * On __Android 11 or higher__: this method will open the system settings page - before that happens 315 * you should explain to the user why your application needs background location permission. 316 * For example, you can use `Modal` component from `react-native` to do that. 317 * > __Note__: Foreground permissions should be granted before asking for the background permissions 318 * (your app can't obtain background permission without foreground permission). 319 * @return A promise that fulfills with an object of type [PermissionResponse](#permissionresponse). 320 */ 321export async function requestBackgroundPermissionsAsync(): Promise<PermissionResponse> { 322 return await ExpoLocation.requestBackgroundPermissionsAsync(); 323} 324 325// @needsAudit 326/** 327 * Check or request permissions for the background location. 328 * This uses both `requestBackgroundPermissionsAsync` and `getBackgroundPermissionsAsync` to 329 * interact with the permissions. 330 * 331 * @example 332 * ```ts 333 * const [status, requestPermission] = Location.useBackgroundPermissions(); 334 * ``` 335 */ 336export const useBackgroundPermissions = createPermissionHook({ 337 getMethod: getBackgroundPermissionsAsync, 338 requestMethod: requestBackgroundPermissionsAsync, 339}); 340 341// --- Location service 342 343// @needsAudit 344/** 345 * Checks whether location services are enabled by the user. 346 * @return A promise which fulfills to `true` if location services are enabled on the device, 347 * or `false` if not. 348 */ 349export async function hasServicesEnabledAsync(): Promise<boolean> { 350 return await ExpoLocation.hasServicesEnabledAsync(); 351} 352 353// --- Background location updates 354 355function _validateTaskName(taskName: string) { 356 if (!taskName || typeof taskName !== 'string') { 357 throw new Error(`\`taskName\` must be a non-empty string. Got ${taskName} instead.`); 358 } 359} 360 361// @docsMissing 362export async function isBackgroundLocationAvailableAsync(): Promise<boolean> { 363 const providerStatus = await getProviderStatusAsync(); 364 return providerStatus.backgroundModeEnabled; 365} 366 367// @needsAudit 368/** 369 * Registers for receiving location updates that can also come when the app is in the background. 370 * 371 * # Task parameters 372 * 373 * Background location task will be receiving following data: 374 * - `locations` - An array of the new locations. 375 * 376 * ```ts 377 * import * as TaskManager from 'expo-task-manager'; 378 * 379 * TaskManager.defineTask(YOUR_TASK_NAME, ({ data: { locations }, error }) => { 380 * if (error) { 381 * // check `error.message` for more details. 382 * return; 383 * } 384 * console.log('Received new locations', locations); 385 * }); 386 * ``` 387 * 388 * @param taskName Name of the task receiving location updates. 389 * @param options An object of options passed to the location manager. 390 * 391 * @return A promise resolving once the task with location updates is registered. 392 */ 393export async function startLocationUpdatesAsync( 394 taskName: string, 395 options: LocationTaskOptions = { accuracy: LocationAccuracy.Balanced } 396): Promise<void> { 397 _validateTaskName(taskName); 398 await ExpoLocation.startLocationUpdatesAsync(taskName, options); 399} 400 401// @needsAudit 402/** 403 * Stops geofencing for specified task. 404 * @param taskName Name of the background location task to stop. 405 * @return A promise resolving as soon as the task is unregistered. 406 */ 407export async function stopLocationUpdatesAsync(taskName: string): Promise<void> { 408 _validateTaskName(taskName); 409 await ExpoLocation.stopLocationUpdatesAsync(taskName); 410} 411 412// @needsAudit 413/** 414 * @param taskName Name of the location task to check. 415 * @return A promise which fulfills with boolean value indicating whether the location task is 416 * started or not. 417 */ 418export async function hasStartedLocationUpdatesAsync(taskName: string): Promise<boolean> { 419 _validateTaskName(taskName); 420 return ExpoLocation.hasStartedLocationUpdatesAsync(taskName); 421} 422 423// --- Geofencing 424 425function _validateRegions(regions: LocationRegion[]) { 426 if (!regions || regions.length === 0) { 427 throw new Error( 428 'Regions array cannot be empty. Use `stopGeofencingAsync` if you want to stop geofencing all regions' 429 ); 430 } 431 for (const region of regions) { 432 if (typeof region.latitude !== 'number') { 433 throw new TypeError(`Region's latitude must be a number. Got '${region.latitude}' instead.`); 434 } 435 if (typeof region.longitude !== 'number') { 436 throw new TypeError( 437 `Region's longitude must be a number. Got '${region.longitude}' instead.` 438 ); 439 } 440 if (typeof region.radius !== 'number') { 441 throw new TypeError(`Region's radius must be a number. Got '${region.radius}' instead.`); 442 } 443 } 444} 445 446// @needsAudit 447/** 448 * Starts geofencing for given regions. When the new event comes, the task with specified name will 449 * be called with the region that the device enter to or exit from. 450 * If you want to add or remove regions from already running geofencing task, you can just call 451 * `startGeofencingAsync` again with the new array of regions. 452 * 453 * # Task parameters 454 * 455 * Geofencing task will be receiving following data: 456 * - `eventType` - Indicates the reason for calling the task, which can be triggered by entering or exiting the region. 457 * See [GeofencingEventType](#geofencingeventtype). 458 * - `region` - Object containing details about updated region. See [LocationRegion](#locationregion) for more details. 459 * 460 * @param taskName Name of the task that will be called when the device enters or exits from specified regions. 461 * @param regions Array of region objects to be geofenced. 462 * 463 * @return A promise resolving as soon as the task is registered. 464 * 465 * @example 466 * ```ts 467 * import { GeofencingEventType } from 'expo-location'; 468 * import * as TaskManager from 'expo-task-manager'; 469 * 470 * TaskManager.defineTask(YOUR_TASK_NAME, ({ data: { eventType, region }, error }) => { 471 * if (error) { 472 * // check `error.message` for more details. 473 * return; 474 * } 475 * if (eventType === GeofencingEventType.Enter) { 476 * console.log("You've entered region:", region); 477 * } else if (eventType === GeofencingEventType.Exit) { 478 * console.log("You've left region:", region); 479 * } 480 * }); 481 * ``` 482 */ 483export async function startGeofencingAsync( 484 taskName: string, 485 regions: LocationRegion[] = [] 486): Promise<void> { 487 _validateTaskName(taskName); 488 _validateRegions(regions); 489 await ExpoLocation.startGeofencingAsync(taskName, { regions }); 490} 491 492// @needsAudit 493/** 494 * Stops geofencing for specified task. It unregisters the background task so the app will not be 495 * receiving any updates, especially in the background. 496 * @param taskName Name of the task to unregister. 497 * @return A promise resolving as soon as the task is unregistered. 498 */ 499export async function stopGeofencingAsync(taskName: string): Promise<void> { 500 _validateTaskName(taskName); 501 await ExpoLocation.stopGeofencingAsync(taskName); 502} 503 504// @needsAudit 505/** 506 * @param taskName Name of the geofencing task to check. 507 * @return A promise which fulfills with boolean value indicating whether the geofencing task is 508 * started or not. 509 */ 510export async function hasStartedGeofencingAsync(taskName: string): Promise<boolean> { 511 _validateTaskName(taskName); 512 return ExpoLocation.hasStartedGeofencingAsync(taskName); 513} 514 515export { LocationEventEmitter as EventEmitter, _getCurrentWatchId }; 516 517export { 518 LocationAccuracy as Accuracy, 519 LocationActivityType as ActivityType, 520 LocationGeofencingEventType as GeofencingEventType, 521 LocationGeofencingRegionState as GeofencingRegionState, 522 PermissionStatus, 523 PermissionHookOptions, 524 setGoogleApiKey, 525}; 526 527export { installWebGeolocationPolyfill } from './GeolocationPolyfill'; 528export * from './Location.types'; 529