implemented disposal guard in files search controller to safely handle async listener notifications
This commit is contained in:
@@ -25,6 +25,16 @@ class FilesSearchController extends ChangeNotifier {
|
|||||||
bool _serverLoading = false;
|
bool _serverLoading = false;
|
||||||
Object? _serverError;
|
Object? _serverError;
|
||||||
int _serverEpoch = 0;
|
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;
|
String get query => _query;
|
||||||
List<String> get pathScope => List.unmodifiable(_pathScope);
|
List<String> get pathScope => List.unmodifiable(_pathScope);
|
||||||
@@ -60,7 +70,7 @@ class FilesSearchController extends ChangeNotifier {
|
|||||||
_serverResults = const [];
|
_serverResults = const [];
|
||||||
_serverLoading = false;
|
_serverLoading = false;
|
||||||
_serverError = null;
|
_serverError = null;
|
||||||
notifyListeners();
|
_safeNotify();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Show loading immediately — even before the (typically fast) cache
|
// 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.
|
// starts typing rather than after the first await hop.
|
||||||
_serverLoading = true;
|
_serverLoading = true;
|
||||||
_serverError = null;
|
_serverError = null;
|
||||||
notifyListeners();
|
_safeNotify();
|
||||||
|
|
||||||
final cacheHits = await searchLocalCaches(_query, pathScope: _pathScope);
|
final cacheHits = await searchLocalCaches(_query, pathScope: _pathScope);
|
||||||
if (epoch != _serverEpoch) return;
|
if (epoch != _serverEpoch) return;
|
||||||
_cacheResults = cacheHits;
|
_cacheResults = cacheHits;
|
||||||
notifyListeners();
|
_safeNotify();
|
||||||
_scheduleServerCall();
|
_scheduleServerCall();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,17 +94,17 @@ class FilesSearchController extends ChangeNotifier {
|
|||||||
_pathScope = const [];
|
_pathScope = const [];
|
||||||
final epoch = ++_serverEpoch;
|
final epoch = ++_serverEpoch;
|
||||||
if (_query.trim().isEmpty) {
|
if (_query.trim().isEmpty) {
|
||||||
notifyListeners();
|
_safeNotify();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_serverLoading = true;
|
_serverLoading = true;
|
||||||
_serverError = null;
|
_serverError = null;
|
||||||
notifyListeners();
|
_safeNotify();
|
||||||
|
|
||||||
final cacheHits = await searchLocalCaches(_query);
|
final cacheHits = await searchLocalCaches(_query);
|
||||||
if (epoch != _serverEpoch) return;
|
if (epoch != _serverEpoch) return;
|
||||||
_cacheResults = cacheHits;
|
_cacheResults = cacheHits;
|
||||||
notifyListeners();
|
_safeNotify();
|
||||||
_scheduleServerCall();
|
_scheduleServerCall();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +116,7 @@ class FilesSearchController extends ChangeNotifier {
|
|||||||
Debouncer.cancel(_debounceTag);
|
Debouncer.cancel(_debounceTag);
|
||||||
_serverLoading = true;
|
_serverLoading = true;
|
||||||
_serverError = null;
|
_serverError = null;
|
||||||
notifyListeners();
|
_safeNotify();
|
||||||
_runServerCall();
|
_runServerCall();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,18 +138,19 @@ class FilesSearchController extends ChangeNotifier {
|
|||||||
.toList();
|
.toList();
|
||||||
_serverLoading = false;
|
_serverLoading = false;
|
||||||
_serverError = null;
|
_serverError = null;
|
||||||
notifyListeners();
|
_safeNotify();
|
||||||
} on Object catch (e) {
|
} on Object catch (e) {
|
||||||
if (epoch != _serverEpoch) return;
|
if (epoch != _serverEpoch) return;
|
||||||
_serverResults = const [];
|
_serverResults = const [];
|
||||||
_serverLoading = false;
|
_serverLoading = false;
|
||||||
_serverError = e;
|
_serverError = e;
|
||||||
notifyListeners();
|
_safeNotify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_disposed = true;
|
||||||
Debouncer.cancel(_debounceTag);
|
Debouncer.cancel(_debounceTag);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user