I am not sure why, if i press start tracking, and let the app run in background, it will accumulate the task, and the next time i return to foreground, it will batch sending webhooks which the timestamp is very close.
import React, { useEffect, useState } from 'react';
import { View, Text, Button, Alert, StyleSheet, SafeAreaView, ScrollView } from 'react-native';
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
const TASK_NAME = 'background-location-task';
function addDebugLog(message: string) {
const timestamp = new Date().toLocaleTimeString();
const formattedLog = `[${timestamp}] ${message}`;
console.log(formattedLog);
}
// ✅ TaskManager definition
TaskManager.defineTask(TASK_NAME, async ({ data, error }: any) => {
addDebugLog("⚠️ Background Task Triggered by OS!");
if (error) {
addDebugLog(`❌ OS Task Error: ${JSON.stringify(error)}`);
return;
}
if (!data) {
addDebugLog("⚠️ Task woke up but data object is completely empty.");
return;
}
if (data && data.locations && data.locations.length > 0) {
const points = data.locations;
const latestLocation = points[points.length - 1];
const { latitude, longitude, accuracy } = latestLocation.coords;
const location= points
if (location) {
try {
// Send directly to your live dashboard
await fetch('https://webhook.site/a98063ef-fa1e-4583-94d4-ed7982b0bc26', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
timestamp: new Date().toISOString(),
locationTimestamp: new Date(latestLocation.timestamp).toISOString(),
coords: location,
}),
});
} catch (err) {
// Fail silently or handle error
}
}
addDebugLog(`📍 Parsed Coordinates -> Lat: ${latitude}, Lon: ${longitude} (±${accuracy}m)`);
} else {
addDebugLog("⚠️ Data layout is missing standard .locations array.");
}
});
export default function IndexRouteScreen() {
const [trackingActive, setTrackingActive] = useState(false);
// Connect the global background logging utility to this component's local state hook
useEffect(() => {
}, []);
useEffect(() => {
addDebugLog("🔄 App Mounted.");
Location.hasStartedLocationUpdatesAsync(TASK_NAME).then((active) => {
addDebugLog(`📋 OS Check: Is background task running? ${active}`);
setTrackingActive(active);
});
}, []);
const startTracking = async () => {
addDebugLog("🚀 Start Tracking button pressed.");
addDebugLog("🔄 Step 1: Requesting Foreground Location Access...");
const { status: fgStatus } = await Location.requestForegroundPermissionsAsync();
addDebugLog(`📋 Foreground Permission status: ${fgStatus}`);
if (fgStatus !== 'granted') {
Alert.alert("Permission Denied", "Foreground location permission is required.");
return;
}
addDebugLog("🔄 Step 2: Requesting Background Location Access...");
const { status: bgStatus } = await Location.requestBackgroundPermissionsAsync();
addDebugLog(`📋 Background Permission status: ${bgStatus}`);
if (bgStatus !== 'granted') {
Alert.alert("Permission Denied", "Background location permission is required. Set your system setting to 'Allow all the time'.");
return;
}
try {
addDebugLog(`🔄 Step 3: Requesting OS to register background task '${TASK_NAME}'...`);
await Location.startLocationUpdatesAsync(TASK_NAME, {
accuracy: Location.Accuracy.High,
timeInterval: 1000, // Reduced to 1 seconds for easier debug testing
distanceInterval: 2, // Reduced to 2 meters for easier debug testing
foregroundService: {
notificationTitle: "Safety Guard Active!!!!!!!!!!!!!!!",
notificationBody: "Monitoring your background location.",
notificationColor: "#FF3B30",
}
});
setTrackingActive(true);
addDebugLog(`🚀 Background task '${TASK_NAME}' successfully registered.`);
Alert.alert("Success", "Background tracking started.");
} catch (err: any) {
addDebugLog(`❌ Task registration aborted by system: ${err.message}`);
Alert.alert("Registration Error", err.message);
}
};
const stopTracking = async () => {
addDebugLog("🛑 Stop Tracking button pressed.");
try {
await Location.stopLocationUpdatesAsync(TASK_NAME);
setTrackingActive(false);
addDebugLog("🛑 Task removed. Location streaming stopped.");
Alert.alert("Stopped", "Location tracking disabled.");
} catch (err: any) {
addDebugLog(`❌ Error caught while stopping task: ${err.message}`);
}
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>Safety Guard</Text>
<Text style={styles.subtitle}>
Status: {trackingActive ? "Active" : "Idle"}
</Text>
{!trackingActive ? (
<Button title="Start Safety Tracking" onPress={startTracking} color="#FF3B30" />
) : (
<Button title="Stop Tracking" onPress={stopTracking} color="#555" />
)}
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#f5f5f5' },
center: { justifyContent: 'center', alignItems: 'center' },
content: { flex: 0.5, justifyContent: 'center', alignItems: 'center', padding: 20 },
title: { fontSize: 32, fontWeight: 'bold', marginBottom: 10 },
subtitle: { fontSize: 16, color: '#666', marginBottom: 30 },
// Debug window styling
debugConsole: { flex: 0.5, backgroundColor: '#1e1e1e', borderTopWidth: 2, borderTopColor: '#333', padding: 10 },
debugTitle: { color: '#00FF00', fontWeight: 'bold', fontSize: 14, marginBottom: 5, fontFamily: 'monospace' },
debugScroll: { flex: 1 },
debugText: { color: '#ffffff', fontSize: 11, fontFamily: 'monospace', marginBottom: 3 },
debugTextEmpty: { color: '#888', fontSize: 11, fontStyle: 'italic' }
});
Here is the problem, and it is a classic Expo Location quirk on Android. Your code is fine, but the OS is not behaving the way you expect due to battery optimization and how Android manages background services.
What is happening: when the app goes to background, Android may freeze the JS execution to save battery. Locations keep piling up in a native buffer. When you return to foreground, restrictions are lifted and Expo dumps the whole accumulated batch into your TaskManager task at once. That is why webhooks fire in a burst with very close timestamps.
Here is how to fix it:
1. Add deferredUpdatesInterval (or deferredUpdatesDistance)
This is the most important part. These parameters tell the native Android layer how often it should wake up JavaScript to deliver data. Without them, Android buffers data however it wants.
await Location.startLocationUpdatesAsync(TASK_NAME, { accuracy: Location.Accuracy.High, timeInterval: 1000, distanceInterval: 2, deferredUpdatesInterval: 10000, deferredUpdatesDistance: 10, foregroundService: { notificationTitle: "Safety Guard Active", notificationBody: "Monitoring your background location.", notificationColor: "#FF3B30", killServiceOnDestroy: false } });
2. Check permissions
Make sure the app has ACCESS_BACKGROUND_LOCATION. In phone settings it must be set to Allow all the time. If it is set to Allow only while using the app, Android will block background tasks.
3. Expo Go limitation
Critical: background tasks with foregroundService do not work in Expo Go. They only work in a Development Build or a release build (APK/AAB). If you are testing in Expo Go, that is the cause.
4. Device manufacturer quirks
Brands like Xiaomi, Huawei, Samsung, Oppo and Vivo have aggressive battery managers that kill background services. You need to go into the phone battery settings and set your app to Unrestricted or Do not optimize. Otherwise the OS will silently suspend your task regardless of what Expo does.
Murasbratan