diff --git a/lib/services/suspected_location_cache.dart b/lib/services/suspected_location_cache.dart index d7ee7e6..bb58480 100644 --- a/lib/services/suspected_location_cache.dart +++ b/lib/services/suspected_location_cache.dart @@ -12,12 +12,11 @@ class SuspectedLocationCache extends ChangeNotifier { SuspectedLocationCache._(); final SuspectedLocationDatabase _database = SuspectedLocationDatabase(); - final Map> _boundsCache = {}; - // Add a synchronous cache for UI responsiveness - // This holds recently fetched bounds data to support synchronous API calls - final Map> _syncCache = {}; - final Set _pendingQueries = {}; + // Simple cache: just hold the currently visible locations + List _currentLocations = []; + String? _currentBoundsKey; + bool _isLoading = false; /// Get suspected locations within specific bounds (async version) Future> getLocationsForBounds(LatLngBounds bounds) async { @@ -25,30 +24,20 @@ class SuspectedLocationCache extends ChangeNotifier { return []; } - final boundsKey = '${bounds.north.toStringAsFixed(4)},${bounds.south.toStringAsFixed(4)},${bounds.east.toStringAsFixed(4)},${bounds.west.toStringAsFixed(4)}'; + final boundsKey = _getBoundsKey(bounds); - // Check cache first - if (_boundsCache.containsKey(boundsKey)) { - return _boundsCache[boundsKey]!; + // If this is the same bounds we're already showing, return current cache + if (boundsKey == _currentBoundsKey) { + return _currentLocations; } try { // Query database for locations in bounds final locations = await _database.getLocationsInBounds(bounds); - // Cache the result in both caches - _boundsCache[boundsKey] = locations; - _syncCache[boundsKey] = locations; - - // Limit cache sizes to prevent memory issues - if (_boundsCache.length > 100) { - final oldestKey = _boundsCache.keys.first; - _boundsCache.remove(oldestKey); - } - if (_syncCache.length > 50) { - final oldestKey = _syncCache.keys.first; - _syncCache.remove(oldestKey); - } + // Update cache + _currentLocations = locations; + _currentBoundsKey = boundsKey; return locations; } catch (e) { @@ -58,56 +47,52 @@ class SuspectedLocationCache extends ChangeNotifier { } /// Get suspected locations within specific bounds (synchronous version for UI) - /// This returns cached data immediately and triggers async fetch if needed + /// Returns current cache immediately, triggers async update if bounds changed List getLocationsForBoundsSync(LatLngBounds bounds) { if (!SuspectedLocationService().isEnabled) { return []; } - final boundsKey = '${bounds.north.toStringAsFixed(4)},${bounds.south.toStringAsFixed(4)},${bounds.east.toStringAsFixed(4)},${bounds.west.toStringAsFixed(4)}'; + final boundsKey = _getBoundsKey(bounds); - // Return sync cache immediately if available - if (_syncCache.containsKey(boundsKey)) { - return _syncCache[boundsKey]!; + // If bounds haven't changed, return current cache immediately + if (boundsKey == _currentBoundsKey) { + return _currentLocations; } - // If not cached and not already being fetched, trigger async fetch - if (!_pendingQueries.contains(boundsKey)) { - _pendingQueries.add(boundsKey); - _fetchAndCacheAsync(bounds, boundsKey); + // Bounds changed - trigger async update but keep showing current cache + if (!_isLoading) { + _isLoading = true; + _updateCacheAsync(bounds, boundsKey); } - // Return empty list immediately (will be updated when async fetch completes) - return []; + // Return current cache (keeps suspected locations visible during map movement) + return _currentLocations; } - /// Async fetch and cache helper - void _fetchAndCacheAsync(LatLngBounds bounds, String boundsKey) async { + /// Simple async update - no complex caching, just swap when done + void _updateCacheAsync(LatLngBounds bounds, String boundsKey) async { try { final locations = await _database.getLocationsInBounds(bounds); - _syncCache[boundsKey] = locations; - _boundsCache[boundsKey] = locations; - - // Limit cache sizes - if (_syncCache.length > 50) { - final oldestKey = _syncCache.keys.first; - _syncCache.remove(oldestKey); + // Only update if this is still the most recent request + if (boundsKey == _getBoundsKey(bounds) || _currentBoundsKey == null) { + _currentLocations = locations; + _currentBoundsKey = boundsKey; + notifyListeners(); // Trigger UI update } - if (_boundsCache.length > 100) { - final oldestKey = _boundsCache.keys.first; - _boundsCache.remove(oldestKey); - } - - // Notify listeners to trigger UI rebuild - notifyListeners(); } catch (e) { - debugPrint('[SuspectedLocationCache] Error in async fetch: $e'); + debugPrint('[SuspectedLocationCache] Error updating cache: $e'); } finally { - _pendingQueries.remove(boundsKey); + _isLoading = false; } } + /// Generate cache key for bounds + String _getBoundsKey(LatLngBounds bounds) { + return '${bounds.north.toStringAsFixed(4)},${bounds.south.toStringAsFixed(4)},${bounds.east.toStringAsFixed(4)},${bounds.west.toStringAsFixed(4)}'; + } + /// Initialize the cache (ensures database is ready) Future loadFromStorage() async { try { @@ -126,10 +111,10 @@ class SuspectedLocationCache extends ChangeNotifier { try { debugPrint('[SuspectedLocationCache] Processing ${rawData.length} raw entries...'); - // Clear all caches since data will change - _boundsCache.clear(); - _syncCache.clear(); - _pendingQueries.clear(); + // Clear cache since data will change + _currentLocations = []; + _currentBoundsKey = null; + _isLoading = false; // Insert data into database in batch await _database.insertBatch(rawData, fetchTime); @@ -146,9 +131,9 @@ class SuspectedLocationCache extends ChangeNotifier { /// Clear all cached data Future clear() async { - _boundsCache.clear(); - _syncCache.clear(); - _pendingQueries.clear(); + _currentLocations = []; + _currentBoundsKey = null; + _isLoading = false; await _database.clearAllData(); notifyListeners(); } diff --git a/lib/services/suspected_location_database.dart b/lib/services/suspected_location_database.dart index 6cf8a36..b893b9f 100644 --- a/lib/services/suspected_location_database.dart +++ b/lib/services/suspected_location_database.dart @@ -73,9 +73,17 @@ class SuspectedLocationDatabase { ) '''); - // Create spatial index for efficient bounds queries + // Create spatial indexes for efficient bounds queries + // Separate indexes for lat and lng for better query optimization await db.execute(''' - CREATE INDEX idx_centroid ON $_tableName ($_columnCentroidLat, $_columnCentroidLng) + CREATE INDEX idx_lat ON $_tableName ($_columnCentroidLat) + '''); + await db.execute(''' + CREATE INDEX idx_lng ON $_tableName ($_columnCentroidLng) + '''); + // Composite index for combined lat/lng queries + await db.execute(''' + CREATE INDEX idx_lat_lng ON $_tableName ($_columnCentroidLat, $_columnCentroidLng) '''); // Metadata table for tracking last fetch time and other info @@ -111,6 +119,7 @@ class SuspectedLocationDatabase { debugPrint('[SuspectedLocationDatabase] Clearing all data...'); // Drop and recreate tables (simpler than DELETE for large datasets) + // Indexes are automatically dropped with tables await db.execute('DROP TABLE IF EXISTS $_tableName'); await db.execute('DROP TABLE IF EXISTS $_metaTableName'); await _createTables(db, _dbVersion);