Skip to content

Commit

Permalink
feat: force refresh with controller
Browse files Browse the repository at this point in the history
  • Loading branch information
clragon committed Feb 2, 2024
1 parent 81c9628 commit 8fba3ee
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 10 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased
### Added
- force refreshing suggestions with `SuggestionsController.refresh`

## 5.1.0 - 2024-01-27
### Added
- Returning null from `suggestionsCallback` hides the box
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,36 @@ As well as all the usual parameters, such as `suggestionsCallback`, `onSelected`
The `decorationBuilder` can be used to inject required wrappers like `Material` or `DefaultTextStyle`.
For more information, see the source code of the `TypeAheadField` widget.

## FAQ

### My suggestions arent changing when I type

You have most likely forgotten to pass the controller and focus node to the `TextField` in the `builder` property.
This is required for the suggestions box to function. Here is an example:

```dart
TypeAheadField(
// ...
controller: myTextEditingController, // your custom controller, or null
builder: (context, controller, focusNode) {
return TextField(
controller: controller, // note how the controller is passed
focusNode: focusNode,
// ...
);
},
);
```

### My suggestions are not updating when I click on the TextField

The TypeAhead field caches the suggestions to avoid unnecessary calls to the `suggestionsCallback`.
If you want to force the suggestions to update, you can use the `SuggestionsController` to force a refresh.

```dart
mySuggestionsController.refresh();
```

## Migrations

### From 4.x to 5.x
Expand Down
3 changes: 3 additions & 0 deletions lib/src/common/base/suggestions_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class SuggestionsController<T> extends ChangeNotifier {

List<T>? _suggestions;

/// Resets the suggestions so that they are requested again.
void refresh() => suggestions = null;

/// Whether the suggestions box is loading.
bool get isLoading => _isLoading;
set isLoading(bool value) {
Expand Down
32 changes: 22 additions & 10 deletions lib/src/common/search/suggestions_search.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,26 +67,38 @@ class _SuggestionsSearchState<T> extends State<SuggestionsSearch<T>> {
bool isQueued = false;
late String search;
late bool wasOpen;
late bool hadSuggestions;

@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();
}
if (wasOpen) load();
});
}

void onChange() {
if (!mounted) return;
onOpenChange();
onSuggestionsChange();
}

void onOpenChange() {
if (wasOpen == widget.controller.isOpen) return;
wasOpen = widget.controller.isOpen;
if (wasOpen) {
load();
}
bool isOpen = widget.controller.isOpen;
if (wasOpen == isOpen) return;
wasOpen = isOpen;
if (isOpen) load();
}

void onSuggestionsChange() {
bool hasSuggestions = widget.controller.suggestions != null;
if (hadSuggestions == hasSuggestions) return;
hadSuggestions = hasSuggestions;
if (!hasSuggestions) load();
}

/// Loads suggestions if not already loaded.
Expand Down Expand Up @@ -143,8 +155,8 @@ class _SuggestionsSearchState<T> extends State<SuggestionsSearch<T>> {
},
child: ConnectorWidget(
value: widget.controller,
connect: (value) => value.addListener(onOpenChange),
disconnect: (value, key) => value.removeListener(onOpenChange),
connect: (value) => value.addListener(onChange),
disconnect: (value, key) => value.removeListener(onChange),
child: widget.child,
),
),
Expand Down
7 changes: 7 additions & 0 deletions test/common/base/suggestions_controller_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ void main() {
expect(controller.suggestions, equals(['a', 'b', 'c']));
});

test('refreshes the suggestions', () {
expect(controller.suggestions, isNull);
controller.suggestions = ['a', 'b', 'c'];
controller.refresh();
expect(controller.suggestions, null);
});

test('sets loading state', () {
expect(controller.isLoading, isFalse);
controller.isLoading = true;
Expand Down
28 changes: 28 additions & 0 deletions test/common/search/suggestions_search_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,34 @@ void main() {
expect(controller.suggestions, ['new1', 'new2']);
});

testWidgets('reloads entries when suggestions are set to null',
(WidgetTester tester) async {
int count = 0;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: SuggestionsSearch<String>(
controller: controller,
textEditingController: textEditingController,
suggestionsCallback: (pattern) async {
count++;
return [count.toString()];
},
debounceDuration: debounceDuration,
child: const SizedBox(),
),
),
),
);

expect(controller.suggestions, ['1']);

controller.refresh();
await tester.pump();

expect(controller.suggestions, ['2']);
});

testWidgets('loads when controller opens', (WidgetTester tester) async {
controller.close();

Expand Down

0 comments on commit 8fba3ee

Please sign in to comment.