From b0ad50f570b3c871901a3291da55b2555d5eda03 Mon Sep 17 00:00:00 2001 From: clragon Date: Sun, 4 Feb 2024 00:47:10 +0100 Subject: [PATCH] fix: suggestions callback being called even when closed --- CHANGELOG.md | 3 ++ .../common/base/suggestions_controller.dart | 33 +++++++++++++------ lib/src/common/field/suggestions_field.dart | 2 +- lib/src/common/search/suggestions_search.dart | 31 +++++++++-------- .../base/suggestions_controller_test.dart | 8 +++-- .../search/suggestions_search_test.dart | 2 ++ 6 files changed, 52 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0736982f..432462fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added - force refreshing suggestions with `SuggestionsController.refresh` +### Fixed +- suggestionsCallback getting called even when closed + ## 5.1.0 - 2024-01-27 ### Added - Returning null from `suggestionsCallback` hides the box diff --git a/lib/src/common/base/suggestions_controller.dart b/lib/src/common/base/suggestions_controller.dart index a01fd5d4..779543ea 100644 --- a/lib/src/common/base/suggestions_controller.dart +++ b/lib/src/common/base/suggestions_controller.dart @@ -52,8 +52,19 @@ class SuggestionsController extends ChangeNotifier { List? _suggestions; + /// A stream of events that occur when the suggestions list should be refreshed. + /// + /// For internal use only. + Stream get $refreshes => _refreshesController.stream; + final StreamController _refreshesController = + StreamController.broadcast(); + /// Resets the suggestions so that they are requested again. - void refresh() => suggestions = null; + void refresh() { + ChangeNotifier.debugAssertNotDisposed(this); + _suggestions = null; + _refreshesController.add(null); + } /// Whether the suggestions box is loading. bool get isLoading => _isLoading; @@ -132,10 +143,18 @@ class SuggestionsController extends ChangeNotifier { /// A stream of events that occur when the suggestions box should be resized. /// /// For internal use only. - Stream get resizes => _resizesController.stream; + Stream get $resizes => _resizesController.stream; final StreamController _resizesController = StreamController.broadcast(); + /// Resizes the suggestions box. + /// + /// You usually don't need to call this method manually. + void resize() { + ChangeNotifier.debugAssertNotDisposed(this); + _resizesController.add(null); + } + /// A stream of selected suggestions. Stream get selections => _selectionsController.stream; final StreamController _selectionsController = @@ -146,14 +165,6 @@ class SuggestionsController extends ChangeNotifier { /// This notifies potential listeners of the selection. void select(T suggestion) => _selectionsController.add(suggestion); - /// Resizes the suggestions box. - /// - /// You usually don't need to call this method manually. - void resize() { - ChangeNotifier.debugAssertNotDisposed(this); - _resizesController.add(null); - } - /// Focuses the suggestions box. void focusBox() { if (_focusState == SuggestionsFocusState.box) return; @@ -204,7 +215,9 @@ class SuggestionsController extends ChangeNotifier { @override void dispose() { close(); + _refreshesController.close(); _resizesController.close(); + _selectionsController.close(); super.dispose(); } } diff --git a/lib/src/common/field/suggestions_field.dart b/lib/src/common/field/suggestions_field.dart index 979fd404..a353e8f7 100644 --- a/lib/src/common/field/suggestions_field.dart +++ b/lib/src/common/field/suggestions_field.dart @@ -273,7 +273,7 @@ class _SuggestionsFieldState extends State> { link: link, child: ConnectorWidget( value: controller, - connect: (value) => value.resizes.listen((_) => onResize()), + connect: (value) => value.$resizes.listen((_) => onResize()), disconnect: (value, key) => key?.cancel(), child: SuggestionsFieldFocusConnector( controller: controller, diff --git a/lib/src/common/search/suggestions_search.dart b/lib/src/common/search/suggestions_search.dart index 922301ec..e90a639e 100644 --- a/lib/src/common/search/suggestions_search.dart +++ b/lib/src/common/search/suggestions_search.dart @@ -65,20 +65,14 @@ class SuggestionsSearch extends StatefulWidget { class _SuggestionsSearchState extends State> { bool isQueued = false; - late String search; - late bool wasOpen; - late bool hadSuggestions; + late String search = widget.textEditingController.text; + late bool wasOpen = widget.controller.isOpen; + late bool hadSuggestions = widget.controller.suggestions != null; @override void initState() { super.initState(); - search = widget.textEditingController.text; - wasOpen = widget.controller.isOpen; - hadSuggestions = widget.controller.suggestions != null; - WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted) return; - if (wasOpen) load(); - }); + WidgetsBinding.instance.addPostFrameCallback((_) => load()); } void onChange() { @@ -91,14 +85,14 @@ class _SuggestionsSearchState extends State> { bool isOpen = widget.controller.isOpen; if (wasOpen == isOpen) return; wasOpen = isOpen; - if (isOpen) load(); + load(); } void onSuggestionsChange() { bool hasSuggestions = widget.controller.suggestions != null; if (hadSuggestions == hasSuggestions) return; hadSuggestions = hasSuggestions; - if (!hasSuggestions) load(); + load(); } /// Loads suggestions if not already loaded. @@ -110,6 +104,7 @@ class _SuggestionsSearchState extends State> { /// Loads suggestions. Discards any previously loaded suggestions. Future reload() async { if (!mounted) return; + if (!wasOpen) return; if (widget.controller.isLoading) { isQueued = true; @@ -151,13 +146,21 @@ class _SuggestionsSearchState extends State> { debounceDuration: widget.debounceDuration, onChanged: (value) { search = value; - reload(); + widget.controller.refresh(); }, child: ConnectorWidget( value: widget.controller, connect: (value) => value.addListener(onChange), disconnect: (value, key) => value.removeListener(onChange), - child: widget.child, + child: ConnectorWidget( + value: widget.controller, + connect: (value) => value.$refreshes.listen((_) { + hadSuggestions = false; // prevents double load + reload(); + }), + disconnect: (value, key) => key?.cancel(), + child: widget.child, + ), ), ), ); diff --git a/test/common/base/suggestions_controller_test.dart b/test/common/base/suggestions_controller_test.dart index ece9f8ab..90c6f01f 100644 --- a/test/common/base/suggestions_controller_test.dart +++ b/test/common/base/suggestions_controller_test.dart @@ -20,11 +20,15 @@ void main() { expect(controller.suggestions, equals(['a', 'b', 'c'])); }); - test('refreshes the suggestions', () { + test('refreshes the suggestions', () async { expect(controller.suggestions, isNull); controller.suggestions = ['a', 'b', 'c']; + bool called = false; + controller.$refreshes.listen((_) => called = true); controller.refresh(); + await Future.value(); expect(controller.suggestions, null); + expect(called, isTrue); }); test('sets loading state', () { @@ -88,7 +92,7 @@ void main() { test('sends resize event', () async { bool called = false; - controller.resizes.listen((_) => called = true); + controller.$resizes.listen((_) => called = true); controller.resize(); await Future.value(); expect(called, isTrue); diff --git a/test/common/search/suggestions_search_test.dart b/test/common/search/suggestions_search_test.dart index 86ead264..2d4d06e9 100644 --- a/test/common/search/suggestions_search_test.dart +++ b/test/common/search/suggestions_search_test.dart @@ -154,6 +154,8 @@ void main() { }); testWidgets('loads when queued up', (WidgetTester tester) async { + controller.open(); + await tester.pumpWidget( MaterialApp( home: Material(