Skip to content

Location

Find out where the user is, which way they face and whether they are walking, turn an address into coordinates and back, and keep tracking in the background or watch for entering a region. @symbiote-native/location wraps expo-location so every SymbioteNative adapter can reach it, not just React. Background updates and geofencing register as tasks through @symbiote-native/task-manager.

Functions and streams live in a shared core. The three permission hooks (useForegroundPermissions, useBackgroundPermissions, useMotionActivityPermissions) are per-adapter, and Angular gets one *PermissionsService each.

OS platform Support
iOS live
Android live
Framework adapter Support
React live
Vue live
Angular live
Svelte live
Solid live
Terminal window
npm install @symbiote-native/location

Scaffolding or extending a SymbioteNative app? npx @symbiote-native/cli new --location (or add --location in an existing app) installs and wires this for you, and asks whether to grant background location - see @symbiote-native/cli.

expo-location and expo-modules-core come along as regular dependencies, pinned to exact versions. Never install either yourself, and never add the expo meta-package to your project (it bundles its own Metro/Babel pipeline, which conflicts with this project’s own).

What the package adds on install (nothing overwrites a value you set):

Platform Added Why
iOS NSLocationWhenInUseUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription, NSLocationAlwaysUsageDescription, NSMotionUsageDescription Permission prompt texts. Reword them to fit your app
iOS UIBackgroundModes: location Without it iOS stops delivering updates once the app backgrounds
Android ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION Ship in expo-location’s own manifest and merge automatically

Android’s background location permissions are a deliberate app choice: requesting ACCESS_BACKGROUND_LOCATION triggers Google Play policy review. The new --location and add --location commands ask whether to grant them. If you said no, or ran non-interactively, run it any time (it is safe to repeat):

Terminal window
npx @symbiote-native/cli grant location

That adds ACCESS_BACKGROUND_LOCATION, FOREGROUND_SERVICE and FOREGROUND_SERVICE_LOCATION to your AndroidManifest.xml. ACTIVITY_RECOGNITION (needed for motion activity) is still added by hand. Pass the foregroundService option to startLocationUpdatesAsync so Android can keep tracking while the app is backgrounded.

Ask for permission, then read the position once or watch it:

import {
getCurrentPositionAsync,
requestForegroundPermissionsAsync,
watchPositionAsync,
} from '@symbiote-native/location';
const { granted } = await requestForegroundPermissionsAsync();
if (granted) {
const position = await getCurrentPositionAsync();
const subscription = await watchPositionAsync({ distanceInterval: 10 }, location => {
console.log(location.coords);
});
// later: subscription.remove();
}

To render the permission state, use the adapter’s own binding. Each is shown for the foreground permission; the background and motion-activity hooks have the same shape.

import { useForegroundPermissions } from '@symbiote-native/location/react';
export default function LocationGate() {
const [status, requestPermission] = useForegroundPermissions();
if (!status?.granted) {
return <button title="Allow location" onPress={() => requestPermission()} />;
}
return <text>Location allowed</text>;
}

These register a task defined with @symbiote-native/task-manager’s defineTask. Define the task at module scope, since native can relaunch the JS bundle headlessly to run it:

// index.ts, alongside AppRegistry.registerComponent
import { defineTask } from '@symbiote-native/task-manager';
const SYNC_TASK = 'background-location-sync';
defineTask(SYNC_TASK, async ({ data, error }) => {
if (error) return;
const { locations } = data as { locations: unknown[] };
await syncLocations(locations);
});

Then start it once the background permission is granted:

import {
requestBackgroundPermissionsAsync,
startLocationUpdatesAsync,
} from '@symbiote-native/location';
const { granted } = await requestBackgroundPermissionsAsync();
if (granted) {
await startLocationUpdatesAsync(SYNC_TASK, { distanceInterval: 100 });
}
Signature Description
getCurrentPositionAsync(options?) Resolves the current position as an ILocationObject
getLastKnownPositionAsync(options?) Resolves the last known position, or null. Filter with maxAge and requiredAccuracy
watchPositionAsync(options, callback, errorHandler?) Calls callback as the position changes. Resolves a subscription; call remove() to stop
getHeadingAsync() Resolves the current compass heading
watchHeadingAsync(callback, errorHandler?) Calls callback as the heading changes. Resolves a subscription
hasServicesEnabledAsync() Whether location services are turned on
getProviderStatusAsync() Resolves the state of the device’s location providers
enableNetworkProviderAsync() Android only. Prompts the user to turn on network location
installWebGeolocationPolyfill() Makes navigator.geolocation call these functions, for libraries that expect it

ILocationOptions takes accuracy (the Accuracy enum, Lowest to BestForNavigation), distanceInterval in meters, and on Android timeInterval in milliseconds and mayShowUserSettingsDialog.

Signature Description
geocodeAsync(address) Resolves the coordinates matching an address string
reverseGeocodeAsync(location) Resolves the addresses matching coordinates
Signature Description
getForegroundPermissionsAsync() / requestForegroundPermissionsAsync() Read or prompt for while-using-the-app access
getBackgroundPermissionsAsync() / requestBackgroundPermissionsAsync() Read or prompt for always access. Ask for foreground first
getMotionActivityPermissionsAsync() / requestMotionActivityPermissionsAsync() Read or prompt for motion activity
Adapter useForegroundPermissions / useBackgroundPermissions / useMotionActivityPermissions
React [status, requestPermission, getPermission]
Vue [status, requestPermission, getPermission], with status a ref
Solid [status, requestPermission, getPermission], with status an accessor
Svelte { status, requestPermission, getPermission }
Angular ForegroundPermissionsService, BackgroundPermissionsService, MotionActivityPermissionsService
Signature Description
getMotionActivityAsync() Resolves the current motion activity (walking, running, driving and so on)
watchMotionActivityAsync(callback, errorHandler?) Calls callback as the activity changes. Foreground only
Signature Description
isBackgroundLocationAvailableAsync() Whether background location is available on this device
startLocationUpdatesAsync(taskName, options?) Starts delivering locations to a task
stopLocationUpdatesAsync(taskName) / hasStartedLocationUpdatesAsync(taskName) Stop updates, or check whether they are running
startGeofencingAsync(taskName, regions) Starts monitoring regions and delivers enter and exit events to a task
stopGeofencingAsync(taskName) / hasStartedGeofencingAsync(taskName) Stop monitoring, or check whether it is running

The package also exports the Accuracy, ActivityType, GeofencingEventType and GeofencingRegionState enums and the ILocation* and IMotionActivity* types.

  • Ask foreground first, then background. Background permission is a separate, stricter prompt. On iOS a user who picks Allow Once for while-using gets access for the current session only.
  • Background tracking has hard limits. It stops if the user terminates the app and resumes when they reopen it. On Android a terminated app does not restart on a location or geofence event; on iOS the system relaunches the app for a new geofence event. Removing the app from the recents list behaves differently per device vendor.
  • Motion activity needs no location permission. It reads the platform’s activity recognition, gated only by getMotionActivityPermissionsAsync and requestMotionActivityPermissionsAsync.
  • watchMotionActivityAsync is foreground only. Updates pause while the app is backgrounded and resume when it returns.
  • Emulators need a location set. In the Android Emulator, enable Settings > Location > Use location and turn off Improve Location Accuracy to feed GPS data. In the iOS Simulator, pick any Features > Location option other than None.
  • No Expo Go warning. Upstream’s one-time background-location warning for Expo Go is dropped: this project never runs under Expo Go.

getCurrentPositionAsync hangs on some Android devices. This is a widely reported problem: the first fix may work and later calls never return, usually on devices with weak or slow location providers. Do not wait forever. Race the call against your own timer, fall back to getLastKnownPositionAsync(), and prefer watchPositionAsync when you need updates.

Location stops updating in the background after a few minutes on Android. The system puts the device to sleep. Pass the foregroundService option to startLocationUpdatesAsync, so Android keeps the tracking alive with a visible notification.

Background permission is denied right after I ask. Ask foreground permission first, then background. If the user chose Allow Once for while-using access, asking for background permission in the same session silently fails and reports denied. iOS cannot tell you whether the user chose Allow Once.

How accurate does it need to be? Higher accuracy uses more battery. Use Accuracy.Balanced for most things, and High or BestForNavigation only while it is visibly needed.

How do I test location in an emulator? Android: Settings > Location > Use location, and turn off Improve Location Accuracy to feed GPS data. iOS Simulator: Features > Location, any option but None.

Does background tracking survive the user killing the app? No. It stops when the user terminates the app. On Android a terminated app does not restart for a location event; on iOS the system relaunches it for a geofence event.

Sources: Expo docs: Location, expo/expo#33981 getCurrentPositionAsync and permissions delays on some Android devices, expo/expo#39851 getCurrentPositionAsync hangs indefinitely, expo/expo#26825 getCurrentPositionAsync never returns the location, expo/expo#14076 location stops updating after 5 to 10 minutes on Android.

expo-location’s JS is hand-ported into this package’s core/ (functions, the watch-subscription machinery shared by position, heading and motion watches, the geolocation polyfill), resolving the native module through expo-modules-core rather than the expo meta-package. Each adapter re-exports core plus its own permission hooks, bound to a shared createPermissionHook factory. The native code is never vendored: expo-modules-autolinking resolves it from node_modules (see the native setup guide).