Skip to content
Support the creatorSupport

Expo background location

Long Ping runs in Expo apps that compile their own native code: a development build, or any project that uses npx expo prebuild to generate its ios/ and android/ folders. It does not run in Expo Go.

There is no Expo config plugin, and the setup does not need one. The iOS keys go in app.json, and the Android permissions, foreground service and boot receiver come from the library’s own manifest.

  • Expo SDK 52 or newer. Long Ping’s peer dependency is react-native >= 0.76.0, and SDK 52 is the first Expo SDK on React Native 0.76.
  • The New Architecture. Long Ping is a TurboModule. Projects created on SDK 52 have "newArchEnabled": true in app.json; from SDK 53 it is the default.
  • iOS 15.1+ and Android 7.0+ (API 24).
  • Google Play Services on Android. Location comes from the fused location provider only.

Expo Go is a prebuilt app with a fixed set of native modules compiled into it. Your JavaScript runs inside it, but you cannot add native code to it, and its Info.plist and Android manifest belong to Expo Go, not to your app.

Background location depends on exactly those parts: the location background mode and the usage strings on iOS, the foreground service and its permissions on Android. Long Ping’s tracking engine is native code as well. In Expo Go, importing react-native-long-ping throws an error saying the LongPing native module could not be found. The fix is to build your own app binary, which is what the rest of this page does.

Terminal window
npx expo install react-native-long-ping

There is no separate pod install step: npx expo run:ios and EAS Build install the LongPing pod when they build.

app.json
{
"expo": {
"name": "Your App",
"slug": "your-app",
"newArchEnabled": true,
"ios": {
"bundleIdentifier": "com.example.yourapp",
"infoPlist": {
"NSLocationWhenInUseUsageDescription": "Your location is shared with dispatch while you are on duty.",
"NSLocationAlwaysAndWhenInUseUsageDescription": "Your location is shared with dispatch while you are on duty, including when the app is closed.",
"UIBackgroundModes": ["location"]
}
},
"android": {
"package": "com.example.yourapp",
"permissions": ["android.permission.POST_NOTIFICATIONS"]
}
}
}

The three ios.infoPlist keys are the same ones a bare React Native app adds to Info.plist in Installation: one usage string for the “While Using” prompt, one for the upgrade to “Always”, and the location background mode. Without the background mode, tracking stops when the app goes to the background, and Long Ping writes an error to its on-device log. Apple reviews the usage strings, so say who sees the location and when.

Expo writes these keys into the native projects when it generates them. If ios/ and android/ already exist from an earlier prebuild, run npx expo prebuild again after changing app.json. If you commit the native folders and edit them by hand, Expo no longer syncs app.json into them; put the keys straight into your Info.plist instead.

Gradle merges the library’s AndroidManifest.xml into your app, whatever android.permissions lists. It brings ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, ACCESS_BACKGROUND_LOCATION, FOREGROUND_SERVICE, FOREGROUND_SERVICE_LOCATION, RECEIVE_BOOT_COMPLETED, WAKE_LOCK, INTERNET and ACCESS_NETWORK_STATE, plus the foreground service (foregroundServiceType="location") and the boot receiver that restarts tracking after a reboot or an app update.

The one Android permission you add is POST_NOTIFICATIONS, listed under android.permissions above. Tracking runs in a foreground service, which always has a notification, and on Android 13 and newer that notification is hidden unless the user grants this permission. Long Ping neither declares nor requests it, and Android only shows the prompt for permissions the app declares.

If your app only tracks while it is open, add "blockedPermissions": ["android.permission.ACCESS_BACKGROUND_LOCATION"] under android. Expo then removes the permission from the merged manifest, the same result as the tools:node="remove" entry in App Store & Google Play review. That page also covers what changes without it, and the Play Console declarations you need if you keep it.

tracking.ts
import { PermissionsAndroid, Platform } from 'react-native';
import LongPing, { AuthorizationStatus } from 'react-native-long-ping';
// Call at startup. Safe on every launch: it never starts, stops or prompts.
export function setUpTracking() {
return LongPing.configure({
movingIntervalSec: 30,
parkedIntervalSec: 180,
notification: { title: 'On duty', text: 'Sharing your location with dispatch' },
});
}
// Call from a button, after your own screen that explains why you need location.
export async function goOnDuty() {
if (Platform.OS === 'android' && Platform.Version >= 33) {
await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
}
const status = await LongPing.requestPermission('always');
if (status === AuthorizationStatus.DENIED || status === AuthorizationStatus.RESTRICTED) {
return false;
}
await LongPing.start();
return true;
}

requestPermission('always') shows the operating system’s two-step flow, “While Using” first and then “Always”; Permissions explains what each platform shows and how to read the status. On Android it needs an Activity in the foreground, so call it from a screen, not from background code. Without an http.url in the config, fixes are recorded on the device but not uploaded; the Quick start adds the upload URL and your access token.

Terminal window
# Local build on a connected device. Generates ios/ and android/ first if they are missing.
npx expo run:ios --device
npx expo run:android --device
# Or build in the cloud with EAS
eas build --platform ios
eas build --platform android

Adding or upgrading Long Ping, or changing the keys above, needs a new build. Over-the-air updates only ship JavaScript and assets, so they cannot add the native module to an existing build. To check the first build, call LongPing.getState() and log the result: if it resolves, the native module is linked.