Implementing Reliable Background Location Tracking in Expo on Android

drashy sesodiadrashy sesodia
4 min read

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:

  1. Install packages

     expo install expo-location expo-task-manager
    
  2. 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"
               }
             }
           ]
         ]
       }
     }
    
  3. 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',
         },
       });
     }
    
  4. 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:

  1. 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"/>
    
  2. 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>
    
  3. 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.

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!

0
Subscribe to my newsletter

Read articles from drashy sesodia directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

drashy sesodia
drashy sesodia