Implementing Reliable Background Location Tracking in Expo on Android


Foreground Service.
Building apps that track user location in the background can be tricky—Android requires a persistent foreground service, special permissions, and a correctly wired-up native project. If you’re using Expo (managed or bare), here’s a step-by-step guide to get background location tracking running smoothly, including how to troubleshoot common Gradle errors around “No matching variant” for React Native modules.
Why a Foreground Service?
Android 8.0+ enforces that any long-running background work needs a visible notification via a foreground service. For location apps, this means:
User trust: they see an ongoing notification (“Tracking your location”)
System compliance: avoids unexpected kills and
MissingForegroundServiceTypeException
Expo’s expo-location
+ expo-task-manager
packages leverage this under-the-hood via a native service—but if you eject (or prebuild to a bare workflow), you must make sure your Android manifest and Gradle setup include the right declarations.
1. In a Managed Expo App
If you never eject, Expo Go handles all the native wiring. All you need is:
Install packages
expo install expo-location expo-task-manager
Configure
app.json
{ "expo": { // … other settings … "android": { "permissions": [ "ACCESS_FINE_LOCATION", "ACCESS_COARSE_LOCATION", "FOREGROUND_SERVICE" ] }, "plugins": [ [ "expo-location", { "foregroundService": { "notificationTitle": "Location Tracking Active", "notificationBody": "We’re recording your location in the background.", "notificationColor": "#FF0000" } } ] ] } }
Define & start your task
// locationTask.js import * as TaskManager from 'expo-task-manager'; export const LOCATION_TASK_NAME = 'BACKGROUND_LOCATION_TASK'; TaskManager.defineTask(LOCATION_TASK_NAME, ({ data, error }) => { if (error) { console.error(error); return; } const { locations } = data; // Send locations to server or update your store… });
// App.js import * as Location from 'expo-location'; import { LOCATION_TASK_NAME } from './locationTask'; async function startTracking() { const { status: fg } = await Location.requestForegroundPermissionsAsync(); const { status: bg } = await Location.requestBackgroundPermissionsAsync(); if (fg !== 'granted' || bg !== 'granted') { return Alert.alert('Permissions required'); } await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, { accuracy: Location.Accuracy.Highest, timeInterval: 5_000, distanceInterval: 10, showsBackgroundLocationIndicator: true, foregroundService: { notificationTitle: 'Tracking your location', notificationBody: 'Tap to return to the app', }, }); }
Rebuild on a real device
# Expo Go won’t run background tasks—use a dev client or standalone: eas build --profile development --platform android
2. Ejecting / Bare Workflow: Getting Your AndroidManifest Right
Once you run expo prebuild
, Expo generates an android/
folder—but does not merge future plugin updates. You must manually confirm:
Permissions before
<application>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
Service declaration inside
<application>
<application …> <!-- Expo Location background-task service --> <service android:name="expo.modules.location.tasks.LocationTaskService" android:exported="false" android:foregroundServiceType="location" /> … </application>
Clean & rebuild
cd android ./gradlew clean cd .. eas build --platform android --profile production
3. Troubleshooting “No matching variant” Gradle Errors
If your build fails during :app:assembleRelease
with messages like:
Could not resolve project :react-native-async-storage_async-storage. No matching variant of project … was found.
…that means your native Gradle setup doesn’t know about those React Native modules. You have two paths:
A) Regenerate Android via Expo
If you’ve made no further custom edits (or can re-apply them):
# From your project root:
rm -rf android
expo prebuild --clean
eas build --platform android --profile production
Expo will autolink all modules (AsyncStorage, DateTimePicker, Maps, Reanimated, etc.) into settings.gradle
and build.gradle
.
B) Manually Link Modules in settings.gradle
If you need to preserve custom native code, open android/settings.gradle
and below include ':app'
add lines like:
include ':react-native-async-storage_async-storage'
project(':react-native-async-storage_async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-async-storage/async-storage/android')
include ':react-native-community_datetimepicker'
project(':react-native-community_datetimepicker').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/datetimepicker/android')
…repeat for each missing module…
Then:
cd android
./gradlew clean
cd ..
eas build --platform android --profile production
4. Wrapping Up
By combining Expo’s plugin-driven approach with a correctly configured AndroidManifest and (if needed) manual Gradle linking, you’ll have:
A robust foreground service that keeps your location task running
All necessary Android permissions, especially
FOREGROUND_SERVICE_LOCATION
on Android 13+Proper linkage of community modules in your bare workflow
With this setup, your users will see a consistent notification, Android will honor your background work, and your EAS builds will complete without XML or variant-matching errors. Happy coding!
Subscribe to my newsletter
Read articles from drashy sesodia directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
