From 15833f3685f5fe977bc50ce41fe04a57f6532b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Sat, 9 May 2026 23:40:04 +0200 Subject: [PATCH] implemented disposal guard in files search controller to safely handle async listener notifications --- .../files/search/files_search_controller.dart | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/view/pages/files/search/files_search_controller.dart b/lib/view/pages/files/search/files_search_controller.dart index ed2fee3..5a75249 100644 --- a/lib/view/pages/files/search/files_search_controller.dart +++ b/lib/view/pages/files/search/files_search_controller.dart @@ -25,6 +25,16 @@ class FilesSearchController extends ChangeNotifier { bool _serverLoading = false; Object? _serverError; int _serverEpoch = 0; + bool _disposed = false; + + /// Guards against the race where the search delegate is closed (and the + /// controller disposed) while a debounced cache scan or server call is + /// still in flight: their late `notifyListeners()` would otherwise throw + /// on a disposed `ChangeNotifier`. + void _safeNotify() { + if (_disposed) return; + _safeNotify(); + } String get query => _query; List get pathScope => List.unmodifiable(_pathScope); @@ -60,7 +70,7 @@ class FilesSearchController extends ChangeNotifier { _serverResults = const []; _serverLoading = false; _serverError = null; - notifyListeners(); + _safeNotify(); return; } // Show loading immediately — even before the (typically fast) cache @@ -68,12 +78,12 @@ class FilesSearchController extends ChangeNotifier { // starts typing rather than after the first await hop. _serverLoading = true; _serverError = null; - notifyListeners(); + _safeNotify(); final cacheHits = await searchLocalCaches(_query, pathScope: _pathScope); if (epoch != _serverEpoch) return; _cacheResults = cacheHits; - notifyListeners(); + _safeNotify(); _scheduleServerCall(); } @@ -84,17 +94,17 @@ class FilesSearchController extends ChangeNotifier { _pathScope = const []; final epoch = ++_serverEpoch; if (_query.trim().isEmpty) { - notifyListeners(); + _safeNotify(); return; } _serverLoading = true; _serverError = null; - notifyListeners(); + _safeNotify(); final cacheHits = await searchLocalCaches(_query); if (epoch != _serverEpoch) return; _cacheResults = cacheHits; - notifyListeners(); + _safeNotify(); _scheduleServerCall(); } @@ -106,7 +116,7 @@ class FilesSearchController extends ChangeNotifier { Debouncer.cancel(_debounceTag); _serverLoading = true; _serverError = null; - notifyListeners(); + _safeNotify(); _runServerCall(); } @@ -128,18 +138,19 @@ class FilesSearchController extends ChangeNotifier { .toList(); _serverLoading = false; _serverError = null; - notifyListeners(); + _safeNotify(); } on Object catch (e) { if (epoch != _serverEpoch) return; _serverResults = const []; _serverLoading = false; _serverError = e; - notifyListeners(); + _safeNotify(); } } @override void dispose() { + _disposed = true; Debouncer.cancel(_debounceTag); super.dispose(); }