From 800331abf4ab19f3593ed3576fa21e9c6d62ae1d Mon Sep 17 00:00:00 2001 From: Perlover Date: Thu, 16 Oct 2025 22:38:04 +0200 Subject: [PATCH] Fix excessive location uploads (two critical issues) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem Analysis (from server logs): - 354,527 POST requests in 24 hours - Only 37,090 unique timestamps - Average: 9-10 duplicates per location - Some locations sent up to 22 times over 8+ hours - Location from 08:55:51 sent at: 00:00, 01:50, 03:40, 05:32, 07:44 Root Causes: 1. **Headless Isolate Cache Issue** - Headless tasks run in separate Dart isolates on Android - LocationCache._last (static in-memory) is null in each isolate - When cache is null, _shouldDelete() returns false - Result: NO filtering applied → locations sent every 1-2 seconds - 37k locations/day instead of normal 2k 2. **Locations Not Deleted After Sync** (CRITICAL) - autoSync: false with manual sync() calls - Plugin accumulates locations in database indefinitely - Each sync() resends ALL accumulated locations - Database grows: same location sent 2hrs later, 4hrs later, etc. - Result: 37k × 9 = 354k requests/day Solutions: 1. **Fallback filtering when cache is null** - Check SharedPreferences directly for lastTimestamp - Apply fastestInterval filtering even without in-memory cache - Prevents excessive location generation in headless mode 2. **Explicit location deletion after sync** - Call destroyLocation() immediately after successful sync() - Prevents database accumulation - Each location sent only once Impact: - Before: 354k requests/day (37k locations × 9 duplicates) - After: ~2k requests/day (normal operation) - Reduction: ~99.4% 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- lib/geolocation_service.dart | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/geolocation_service.dart b/lib/geolocation_service.dart index 955dd13..bda0c4f 100644 --- a/lib/geolocation_service.dart +++ b/lib/geolocation_service.dart @@ -54,7 +54,13 @@ class GeolocationService { } else { LocationCache.set(location); try { + // Sync current location to server await bg.BackgroundGeolocation.sync(); + + // CRITICAL FIX: Destroy the location after successful sync + // Without this, locations accumulate in the plugin's database + // and get resent every time sync() is called + await bg.BackgroundGeolocation.destroyLocation(location.uuid); } catch (error) { developer.log('Failed to send location', error: error); } @@ -66,7 +72,26 @@ class GeolocationService { if (location.extras?.isNotEmpty == true) return false; final lastLocation = LocationCache.get(); - if (lastLocation == null) return false; + + // If cache is null (e.g., in headless isolate), check SharedPreferences directly + if (lastLocation == null) { + final lastTimestamp = Preferences.instance.getString(Preferences.lastTimestamp); + if (lastTimestamp != null) { + // Not the first location ever - apply minimum filtering to prevent excessive uploads + final duration = DateTime.parse(location.timestamp).difference(DateTime.parse(lastTimestamp)).inSeconds; + final isHighestAccuracy = Preferences.instance.getString(Preferences.accuracy) == 'highest'; + + if (!isHighestAccuracy) { + final fastestInterval = Preferences.instance.getInt(Preferences.fastestInterval); + if (fastestInterval != null && duration < fastestInterval) { + developer.log('Location too frequent (${duration}s < ${fastestInterval}s), deleting'); + return true; + } + } + } + // First location or sufficient time passed + return false; + } final isHighestAccuracy = Preferences.instance.getString(Preferences.accuracy) == 'highest'; final duration = DateTime.parse(location.timestamp).difference(DateTime.parse(lastLocation.timestamp)).inSeconds;