Skip to content

Commit b9d98a9

Browse files
authored
Typeahead fixes (#599)
* Added tests for FormBuilderTypeahead * Fix issue: Typeahead change doesn't fire didChange & onChanged. Fixes #595 Also ensure programatically changed value propagates to UI and initialValue is set in UI after reset
1 parent dde20f1 commit b9d98a9

File tree

2 files changed

+88
-6
lines changed

2 files changed

+88
-6
lines changed

lib/src/fields/form_builder_typeahead.dart

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ class FormBuilderTypeAhead<T> extends FormBuilderField<T> {
325325
),
326326
focusNode: state.effectiveFocusNode,
327327
decoration: state.decoration(),
328-
) as TextFieldConfiguration<
329-
dynamic>, // HACK to satisfy strictness
328+
) as TextFieldConfiguration<dynamic>,
329+
// HACK to satisfy strictness
330330
suggestionsCallback: suggestionsCallback,
331331
itemBuilder: itemBuilder,
332332
transitionBuilder: (context, suggestionsBox, controller) =>
@@ -339,9 +339,7 @@ class FormBuilderTypeAhead<T> extends FormBuilderField<T> {
339339
state._typeAheadController.text =
340340
suggestion != null ? suggestion.toString() : '';
341341
}
342-
if (onSuggestionSelected != null) {
343-
onSuggestionSelected(suggestion);
344-
}
342+
onSuggestionSelected?.call(suggestion);
345343
},
346344
getImmediateSuggestions: getImmediateSuggestions,
347345
errorBuilder: errorBuilder,
@@ -380,6 +378,29 @@ class _FormBuilderTypeAheadState<T>
380378
super.initState();
381379
_typeAheadController = widget.controller ??
382380
TextEditingController(text: widget.initialValue?.toString());
381+
_typeAheadController.addListener(_handleControllerChanged);
382+
}
383+
384+
void _handleControllerChanged() {
385+
// Suppress changes that originated from within this class.
386+
//
387+
// In the case where a controller has been passed in to this widget, we
388+
// register this change listener. In these cases, we'll also receive change
389+
// notifications for changes originating from within this class -- for
390+
// example, the reset() method. In such cases, the FormField value will
391+
// already have been set.
392+
if (_typeAheadController.text != value) {
393+
didChange(_typeAheadController.text as T);
394+
}
395+
}
396+
397+
@override
398+
void didChange(T value) {
399+
super.didChange(value);
400+
401+
if (_typeAheadController.text != value) {
402+
_typeAheadController.text = value.toString();
403+
}
383404
}
384405

385406
@override
@@ -394,6 +415,6 @@ class _FormBuilderTypeAheadState<T>
394415
@override
395416
void reset() {
396417
super.reset();
397-
_typeAheadController.text = widget.initialValue?.toString();
418+
_typeAheadController.text = initialValue?.toString();
398419
}
399420
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_form_builder/flutter_form_builder.dart';
3+
import 'package:flutter_test/flutter_test.dart';
4+
5+
import 'form_builder_tester.dart';
6+
7+
void main() {
8+
testWidgets('FormBuilderTypeahead -- Two', (WidgetTester tester) async {
9+
const options = ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven'];
10+
const initialTextValue = 'One';
11+
const newTextValue = 'Two';
12+
const textFieldName = 'typeahead1';
13+
final textEditingController = TextEditingController();
14+
final testWidgetKey = GlobalKey<FormBuilderFieldState>();
15+
16+
final testWidget = FormBuilderTypeAhead<String>(
17+
key: testWidgetKey,
18+
name: textFieldName,
19+
initialValue: initialTextValue,
20+
controller: textEditingController,
21+
itemBuilder: (context, country) {
22+
return ListTile(title: Text(country));
23+
},
24+
suggestionsCallback: (query) {
25+
if (query.isNotEmpty) {
26+
var lowercaseQuery = query.toLowerCase();
27+
return options.where((country) {
28+
return country.toLowerCase().contains(lowercaseQuery);
29+
}).toList(growable: false)
30+
..sort((a, b) => a
31+
.toLowerCase()
32+
.indexOf(lowercaseQuery)
33+
.compareTo(b.toLowerCase().indexOf(lowercaseQuery)));
34+
} else {
35+
return options;
36+
}
37+
},
38+
);
39+
final widgetFinder = find.byWidget(testWidget);
40+
41+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
42+
expect(formSave(), isTrue);
43+
expect(formValue(textFieldName), initialTextValue);
44+
45+
// await tester.enterText(widgetFinder, newTextValue);
46+
textEditingController.text = newTextValue;
47+
expect(formSave(), isTrue);
48+
expect(formValue(textFieldName), equals(newTextValue));
49+
50+
// await tester.enterText(widgetFinder, newTextValue);
51+
testWidgetKey.currentState.didChange(initialTextValue);
52+
expect(textEditingController.text, initialTextValue);
53+
expect(formSave(), isTrue);
54+
expect(formValue(textFieldName), equals(initialTextValue));
55+
56+
// await tester.enterText(widgetFinder, '');
57+
textEditingController.text = '';
58+
expect(formSave(), isTrue);
59+
expect(formValue(textFieldName), isEmpty);
60+
});
61+
}

0 commit comments

Comments
 (0)