diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies deleted file mode 100644 index f95da3ec..00000000 --- a/example/.flutter-plugins-dependencies +++ /dev/null @@ -1 +0,0 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_keyboard_visibility","path":"/Users/sjm/Development/tools/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_keyboard_visibility-4.0.2/","dependencies":[]}],"android":[{"name":"flutter_keyboard_visibility","path":"/Users/sjm/Development/tools/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_keyboard_visibility-4.0.2/","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[{"name":"flutter_keyboard_visibility_web","path":"/Users/sjm/Development/tools/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_keyboard_visibility_web-1.0.1/","dependencies":[]}]},"dependencyGraph":[{"name":"flutter_keyboard_visibility","dependencies":["flutter_keyboard_visibility_web"]},{"name":"flutter_keyboard_visibility_web","dependencies":[]}],"date_created":"2021-01-11 14:24:04.606602","version":"1.22.5"} \ No newline at end of file diff --git a/example/.gitignore b/example/.gitignore index dee655cc..086683e5 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -7,3 +7,4 @@ build/ .flutter-plugins +.flutter-plugins-dependencies \ No newline at end of file diff --git a/example/lib/cupertino_app.dart b/example/lib/cupertino_app.dart index c523deb2..50ad6de3 100644 --- a/example/lib/cupertino_app.dart +++ b/example/lib/cupertino_app.dart @@ -1,7 +1,8 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter_typeahead/cupertino_flutter_typeahead.dart'; +import 'dart:async'; import 'package:example/data.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_typeahead/cupertino_flutter_typeahead.dart'; class MyCupertinoApp extends StatelessWidget { @override diff --git a/example/lib/data.dart b/example/lib/data.dart index 4495b5e7..d7cfa8cc 100644 --- a/example/lib/data.dart +++ b/example/lib/data.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:math'; class BackendService { @@ -28,7 +29,7 @@ class CitiesService { ]; static List getSuggestions(String query) { - List matches = List(); + List matches = []; matches.addAll(cities); matches.retainWhere((s) => s.toLowerCase().contains(query.toLowerCase())); diff --git a/example/pubspec.lock b/example/pubspec.lock index a3a22265..b1ee9f02 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,42 +7,42 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.5.0-nullsafety.3" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0-nullsafety.3" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0-nullsafety.5" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0-nullsafety.3" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0-nullsafety.5" cupertino_icons: dependency: "direct main" description: @@ -56,7 +56,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" flutter: dependency: "direct main" description: flutter @@ -68,21 +68,21 @@ packages: name: flutter_keyboard_visibility url: "https://pub.dartlang.org" source: hosted - version: "4.0.2" + version: "5.0.0-nullsafety.0" flutter_keyboard_visibility_platform_interface: dependency: transitive description: name: flutter_keyboard_visibility_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0-nullsafety.0" flutter_keyboard_visibility_web: dependency: transitive description: name: flutter_keyboard_visibility_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0-nullsafety.0" flutter_test: dependency: "direct dev" description: flutter @@ -94,40 +94,47 @@ packages: path: ".." relative: true source: path - version: "2.0.0" + version: "2.1.0-nullsafety.0" flutter_web_plugins: dependency: transitive description: flutter source: sdk version: "0.0.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3-nullsafety.3" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.10-nullsafety.3" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0-nullsafety.6" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0-nullsafety.3" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.1.0-nullsafety.1" sky_engine: dependency: transitive description: flutter @@ -139,56 +146,56 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" + version: "1.8.0-nullsafety.4" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.1" + version: "1.10.0-nullsafety.6" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0-nullsafety.3" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0-nullsafety.3" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" + version: "0.2.19-nullsafety.6" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0-nullsafety.5" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0-nullsafety.5" sdks: - dart: ">=2.10.0-110 <2.11.0" + dart: ">=2.12.0-29.10.beta <3.0.0" flutter: ">=1.20.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index db7c78b8..51b863df 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -13,7 +13,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - +environment: + sdk: '>=1.19.0 <3.0.0' # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec diff --git a/lib/cupertino_flutter_typeahead.dart b/lib/cupertino_flutter_typeahead.dart index 48f7cd15..4cd6bca3 100644 --- a/lib/cupertino_flutter_typeahead.dart +++ b/lib/cupertino_flutter_typeahead.dart @@ -26,10 +26,10 @@ import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; typedef FutureOr> SuggestionsCallback(String pattern); typedef Widget ItemBuilder(BuildContext context, T itemData); typedef void SuggestionSelectionCallback(T suggestion); -typedef Widget ErrorBuilder(BuildContext context, Object error); +typedef Widget ErrorBuilder(BuildContext context, Object? error); typedef AnimationTransitionBuilder( - BuildContext context, Widget child, AnimationController controller); + BuildContext context, Widget child, AnimationController? controller); // Cupertino BoxDecoration taken from flutter/lib/src/cupertino/text_field.dart const BorderSide _kDefaultRoundedBorderSide = BorderSide( @@ -70,33 +70,30 @@ class CupertinoTypeAheadFormField extends FormField { /// Creates a [CupertinoTypeAheadFormField] CupertinoTypeAheadFormField( - {Key key, - String initialValue, + {Key? key, + String? initialValue, bool getImmediateSuggestions: false, @Deprecated('Use autoValidateMode parameter which provides more specific ' 'behavior related to auto validation. ' 'This feature was deprecated after Flutter v1.19.0.') bool autovalidate: false, bool enabled: true, - AutovalidateMode autovalidateMode, - FormFieldSetter onSaved, - FormFieldValidator validator, - ErrorBuilder errorBuilder, - WidgetBuilder noItemsFoundBuilder, - WidgetBuilder loadingBuilder, + AutovalidateMode? autovalidateMode, + FormFieldSetter? onSaved, + FormFieldValidator? validator, + ErrorBuilder? errorBuilder, + WidgetBuilder? noItemsFoundBuilder, + WidgetBuilder? loadingBuilder, Duration debounceDuration: const Duration(milliseconds: 300), CupertinoSuggestionsBoxDecoration suggestionsBoxDecoration: const CupertinoSuggestionsBoxDecoration(), - CupertinoSuggestionsBoxController suggestionsBoxController, - @required - SuggestionSelectionCallback onSuggestionSelected, - @required - ItemBuilder itemBuilder, - @required - SuggestionsCallback suggestionsCallback, + CupertinoSuggestionsBoxController? suggestionsBoxController, + required SuggestionSelectionCallback onSuggestionSelected, + required ItemBuilder itemBuilder, + required SuggestionsCallback suggestionsCallback, double suggestionsBoxVerticalOffset: 5.0, this.textFieldConfiguration: const CupertinoTextFieldConfiguration(), - AnimationTransitionBuilder transitionBuilder, + AnimationTransitionBuilder? transitionBuilder, Duration animationDuration: const Duration(milliseconds: 500), double animationStart: 0.25, AxisDirection direction: AxisDirection.down, @@ -114,12 +111,13 @@ class CupertinoTypeAheadFormField extends FormField { onSaved: onSaved, validator: validator, initialValue: textFieldConfiguration.controller != null - ? textFieldConfiguration.controller.text + ? textFieldConfiguration.controller!.text : (initialValue ?? ''), enabled: enabled, autovalidateMode: autovalidateMode, builder: (FormFieldState field) { - final _CupertinoTypeAheadFormFieldState state = field; + final _CupertinoTypeAheadFormFieldState state = + field as _CupertinoTypeAheadFormFieldState; return CupertinoTypeAheadField( getImmediateSuggestions: getImmediateSuggestions, @@ -133,7 +131,7 @@ class CupertinoTypeAheadFormField extends FormField { textFieldConfiguration: textFieldConfiguration.copyWith( onChanged: (text) { state.didChange(text); - textFieldConfiguration.onChanged(text); + textFieldConfiguration.onChanged!(text); }, controller: state._effectiveController, ), @@ -161,13 +159,14 @@ class CupertinoTypeAheadFormField extends FormField { } class _CupertinoTypeAheadFormFieldState extends FormFieldState { - TextEditingController _controller; + TextEditingController? _controller; - TextEditingController get _effectiveController => + TextEditingController? get _effectiveController => widget.textFieldConfiguration.controller ?? _controller; @override - CupertinoTypeAheadFormField get widget => super.widget; + CupertinoTypeAheadFormField get widget => + super.widget as CupertinoTypeAheadFormField; @override void initState() { @@ -175,7 +174,7 @@ class _CupertinoTypeAheadFormFieldState extends FormFieldState { if (widget.textFieldConfiguration.controller == null) { _controller = TextEditingController(text: widget.initialValue); } else { - widget.textFieldConfiguration.controller + widget.textFieldConfiguration.controller! .addListener(_handleControllerChanged); } } @@ -193,9 +192,9 @@ class _CupertinoTypeAheadFormFieldState extends FormFieldState { if (oldWidget.textFieldConfiguration.controller != null && widget.textFieldConfiguration.controller == null) _controller = TextEditingController.fromValue( - oldWidget.textFieldConfiguration.controller.value); + oldWidget.textFieldConfiguration.controller!.value); if (widget.textFieldConfiguration.controller != null) { - setValue(widget.textFieldConfiguration.controller.text); + setValue(widget.textFieldConfiguration.controller!.text); if (oldWidget.textFieldConfiguration.controller == null) _controller = null; } @@ -213,7 +212,7 @@ class _CupertinoTypeAheadFormFieldState extends FormFieldState { void reset() { super.reset(); setState(() { - _effectiveController.text = widget.initialValue; + _effectiveController!.text = widget.initialValue!; }); } @@ -225,8 +224,8 @@ class _CupertinoTypeAheadFormFieldState extends FormFieldState { // notifications for changes originating from within this class -- for // example, the reset() method. In such cases, the FormField value will // already have been set. - if (_effectiveController.text != value) - didChange(_effectiveController.text); + if (_effectiveController!.text != value) + didChange(_effectiveController!.text); } } @@ -306,7 +305,7 @@ class CupertinoTypeAheadField extends StatefulWidget { /// Used to control the `_SuggestionsBox`. Allows manual control to /// open, close, toggle, or resize the `_SuggestionsBox`. - final CupertinoSuggestionsBoxController suggestionsBoxController; + final CupertinoSuggestionsBoxController? suggestionsBoxController; /// The duration to wait after the user stops typing before calling /// [suggestionsCallback] @@ -328,7 +327,7 @@ class CupertinoTypeAheadField extends StatefulWidget { /// ``` /// /// If not specified, a [CupertinoActivityIndicator](https://docs.flutter.io/flutter/cupertino/CupertinoActivityIndicator-class.html) is shown - final WidgetBuilder loadingBuilder; + final WidgetBuilder? loadingBuilder; /// Called when [suggestionsCallback] returns an empty array. /// @@ -342,7 +341,7 @@ class CupertinoTypeAheadField extends StatefulWidget { /// ``` /// /// If not specified, a simple text is shown - final WidgetBuilder noItemsFoundBuilder; + final WidgetBuilder? noItemsFoundBuilder; /// Called when [suggestionsCallback] throws an exception. /// @@ -354,7 +353,7 @@ class CupertinoTypeAheadField extends StatefulWidget { /// return Text('$error'); /// } /// ``` - final ErrorBuilder errorBuilder; + final ErrorBuilder? errorBuilder; /// Called to display animations when [suggestionsCallback] returns suggestions /// @@ -380,7 +379,7 @@ class CupertinoTypeAheadField extends StatefulWidget { /// To fully remove the animation, just return `suggestionsBox` /// /// If not specified, a [SizeTransition](https://docs.flutter.io/flutter/widgets/SizeTransition-class.html) is shown. - final AnimationTransitionBuilder transitionBuilder; + final AnimationTransitionBuilder? transitionBuilder; /// The duration that [transitionBuilder] animation takes. /// @@ -481,10 +480,10 @@ class CupertinoTypeAheadField extends StatefulWidget { /// Creates a [CupertinoTypeAheadField] CupertinoTypeAheadField( - {Key key, - @required this.suggestionsCallback, - @required this.itemBuilder, - @required this.onSuggestionSelected, + {Key? key, + required this.suggestionsCallback, + required this.itemBuilder, + required this.onSuggestionSelected, this.textFieldConfiguration: const CupertinoTextFieldConfiguration(), this.suggestionsBoxDecoration: const CupertinoSuggestionsBoxDecoration(), this.debounceDuration: const Duration(milliseconds: 300), @@ -505,17 +504,7 @@ class CupertinoTypeAheadField extends StatefulWidget { this.keepSuggestionsOnLoading: true, this.keepSuggestionsOnSuggestionSelected: false, this.autoFlipDirection: false}) - : assert(suggestionsCallback != null), - assert(itemBuilder != null), - assert(onSuggestionSelected != null), - assert(animationStart != null && - animationStart >= 0.0 && - animationStart <= 1.0), - assert(animationDuration != null), - assert(debounceDuration != null), - assert(textFieldConfiguration != null), - assert(suggestionsBoxDecoration != null), - assert(suggestionsBoxVerticalOffset != null), + : assert(animationStart >= 0.0 && animationStart <= 1.0), assert( direction == AxisDirection.down || direction == AxisDirection.up), super(key: key); @@ -527,43 +516,43 @@ class CupertinoTypeAheadField extends StatefulWidget { class _CupertinoTypeAheadFieldState extends State> with WidgetsBindingObserver { - FocusNode _focusNode; - TextEditingController _textEditingController; - _CupertinoSuggestionsBox _suggestionsBox; + FocusNode? _focusNode; + TextEditingController? _textEditingController; + _CupertinoSuggestionsBox? _suggestionsBox; - TextEditingController get _effectiveController => + TextEditingController? get _effectiveController => widget.textFieldConfiguration.controller ?? _textEditingController; - FocusNode get _effectiveFocusNode => + FocusNode? get _effectiveFocusNode => widget.textFieldConfiguration.focusNode ?? _focusNode; - VoidCallback _focusNodeListener; + late VoidCallback _focusNodeListener; final LayerLink _layerLink = LayerLink(); // Timer that resizes the suggestion box on each tick. Only active when the user is scrolling. - Timer _resizeOnScrollTimer; + Timer? _resizeOnScrollTimer; // The rate at which the suggestion box will resize when the user is scrolling final Duration _resizeOnScrollRefreshRate = const Duration(milliseconds: 500); // Will have a value if the typeahead is inside a scrollable widget - ScrollPosition _scrollPosition; + ScrollPosition? _scrollPosition; // Keyboard detection final Stream _keyboardVisibility = KeyboardVisibilityController().onChange; - StreamSubscription _keyboardVisibilitySubscription; + late StreamSubscription _keyboardVisibilitySubscription; @override void didChangeMetrics() { // Catch keyboard event and orientation change; resize suggestions list - this._suggestionsBox.onChangeMetrics(); + this._suggestionsBox!.onChangeMetrics(); } @override void dispose() { - this._suggestionsBox.close(); - this._suggestionsBox.widgetMounted = false; - WidgetsBinding.instance.removeObserver(this); + this._suggestionsBox!.close(); + this._suggestionsBox!.widgetMounted = false; + WidgetsBinding.instance!.removeObserver(this); _keyboardVisibilitySubscription.cancel(); - _effectiveFocusNode.removeListener(_focusNodeListener); + _effectiveFocusNode!.removeListener(_focusNodeListener); _focusNode?.dispose(); _resizeOnScrollTimer?.cancel(); _scrollPosition?.removeListener(_scrollResizeListener); @@ -573,7 +562,7 @@ class _CupertinoTypeAheadFieldState extends State> @override void initState() { super.initState(); - WidgetsBinding.instance.addObserver(this); + WidgetsBinding.instance!.addObserver(this); if (widget.textFieldConfiguration.controller == null) { this._textEditingController = TextEditingController(); @@ -590,32 +579,32 @@ class _CupertinoTypeAheadFieldState extends State> this._effectiveFocusNode; this._focusNodeListener = () { - if (_effectiveFocusNode.hasFocus) { - this._suggestionsBox.open(); + if (_effectiveFocusNode!.hasFocus) { + this._suggestionsBox!.open(); } else { - this._suggestionsBox.close(); + this._suggestionsBox!.close(); } }; - this._effectiveFocusNode.addListener(_focusNodeListener); + this._effectiveFocusNode!.addListener(_focusNodeListener); // hide suggestions box on keyboard closed this._keyboardVisibilitySubscription = _keyboardVisibility.listen((bool isVisible) { if (widget.hideSuggestionsOnKeyboardHide && !isVisible) { - _effectiveFocusNode.unfocus(); + _effectiveFocusNode!.unfocus(); } }); - WidgetsBinding.instance.addPostFrameCallback((duration) { + WidgetsBinding.instance!.addPostFrameCallback((duration) { if (mounted) { this._initOverlayEntry(); // calculate initial suggestions list size - this._suggestionsBox.resize(); + this._suggestionsBox!.resize(); // in case we already missed the focus event - if (this._effectiveFocusNode.hasFocus) { - this._suggestionsBox.open(); + if (this._effectiveFocusNode!.hasFocus) { + this._suggestionsBox!.open(); } } }); @@ -624,33 +613,33 @@ class _CupertinoTypeAheadFieldState extends State> @override void didChangeDependencies() { super.didChangeDependencies(); - ScrollableState scrollableState = Scrollable.of(context); + ScrollableState? scrollableState = Scrollable.of(context); if (scrollableState != null) { // The TypeAheadField is inside a scrollable widget _scrollPosition = scrollableState.position; - _scrollPosition.removeListener(_scrollResizeListener); - _scrollPosition.isScrollingNotifier.addListener(_scrollResizeListener); + _scrollPosition!.removeListener(_scrollResizeListener); + _scrollPosition!.isScrollingNotifier.addListener(_scrollResizeListener); } } void _scrollResizeListener() { - bool isScrolling = _scrollPosition.isScrollingNotifier.value; + bool isScrolling = _scrollPosition!.isScrollingNotifier.value; _resizeOnScrollTimer?.cancel(); if (isScrolling) { // Scroll started _resizeOnScrollTimer = Timer.periodic(_resizeOnScrollRefreshRate, (timer) { - _suggestionsBox.resize(); + _suggestionsBox!.resize(); }); } else { // Scroll finished - _suggestionsBox.resize(); + _suggestionsBox!.resize(); } } void _initOverlayEntry() { - this._suggestionsBox._overlayEntry = OverlayEntry(builder: (context) { + this._suggestionsBox!._overlayEntry = OverlayEntry(builder: (context) { final suggestionsList = _SuggestionsList( suggestionsBox: _suggestionsBox, decoration: widget.suggestionsBoxDecoration, @@ -666,35 +655,35 @@ class _CupertinoTypeAheadFieldState extends State> getImmediateSuggestions: widget.getImmediateSuggestions, onSuggestionSelected: (T selection) { if (!widget.keepSuggestionsOnSuggestionSelected) { - this._effectiveFocusNode.unfocus(); - this._suggestionsBox.close(); + this._effectiveFocusNode!.unfocus(); + this._suggestionsBox!.close(); } widget.onSuggestionSelected(selection); }, itemBuilder: widget.itemBuilder, - direction: _suggestionsBox.direction, + direction: _suggestionsBox!.direction, hideOnLoading: widget.hideOnLoading, hideOnEmpty: widget.hideOnEmpty, hideOnError: widget.hideOnError, keepSuggestionsOnLoading: widget.keepSuggestionsOnLoading, ); - double w = _suggestionsBox.textBoxWidth; + double w = _suggestionsBox!.textBoxWidth; if (widget.suggestionsBoxDecoration.constraints != null) { - if (widget.suggestionsBoxDecoration.constraints.minWidth != 0.0 && - widget.suggestionsBoxDecoration.constraints.maxWidth != + if (widget.suggestionsBoxDecoration.constraints!.minWidth != 0.0 && + widget.suggestionsBoxDecoration.constraints!.maxWidth != double.infinity) { - w = (widget.suggestionsBoxDecoration.constraints.minWidth + - widget.suggestionsBoxDecoration.constraints.maxWidth) / + w = (widget.suggestionsBoxDecoration.constraints!.minWidth + + widget.suggestionsBoxDecoration.constraints!.maxWidth) / 2; - } else if (widget.suggestionsBoxDecoration.constraints.minWidth != + } else if (widget.suggestionsBoxDecoration.constraints!.minWidth != 0.0 && - widget.suggestionsBoxDecoration.constraints.minWidth > w) { - w = widget.suggestionsBoxDecoration.constraints.minWidth; - } else if (widget.suggestionsBoxDecoration.constraints.maxWidth != + widget.suggestionsBoxDecoration.constraints!.minWidth > w) { + w = widget.suggestionsBoxDecoration.constraints!.minWidth; + } else if (widget.suggestionsBoxDecoration.constraints!.maxWidth != double.infinity && - widget.suggestionsBoxDecoration.constraints.maxWidth < w) { - w = widget.suggestionsBoxDecoration.constraints.maxWidth; + widget.suggestionsBoxDecoration.constraints!.maxWidth < w) { + w = widget.suggestionsBoxDecoration.constraints!.maxWidth; } } @@ -705,11 +694,11 @@ class _CupertinoTypeAheadFieldState extends State> showWhenUnlinked: false, offset: Offset( widget.suggestionsBoxDecoration.offsetX, - _suggestionsBox.direction == AxisDirection.down - ? _suggestionsBox.textBoxHeight + + _suggestionsBox!.direction == AxisDirection.down + ? _suggestionsBox!.textBoxHeight + widget.suggestionsBoxVerticalOffset - : _suggestionsBox.directionUpOffset), - child: _suggestionsBox.direction == AxisDirection.down + : _suggestionsBox!.directionUpOffset), + child: _suggestionsBox!.direction == AxisDirection.down ? suggestionsList : FractionalTranslation( translation: @@ -767,28 +756,28 @@ class _CupertinoTypeAheadFieldState extends State> } class _SuggestionsList extends StatefulWidget { - final _CupertinoSuggestionsBox suggestionsBox; - final TextEditingController controller; + final _CupertinoSuggestionsBox? suggestionsBox; + final TextEditingController? controller; final bool getImmediateSuggestions; - final SuggestionSelectionCallback onSuggestionSelected; - final SuggestionsCallback suggestionsCallback; - final ItemBuilder itemBuilder; - final CupertinoSuggestionsBoxDecoration decoration; - final Duration debounceDuration; - final WidgetBuilder loadingBuilder; - final WidgetBuilder noItemsFoundBuilder; - final ErrorBuilder errorBuilder; - final AnimationTransitionBuilder transitionBuilder; - final Duration animationDuration; - final double animationStart; - final AxisDirection direction; - final bool hideOnLoading; - final bool hideOnEmpty; - final bool hideOnError; - final bool keepSuggestionsOnLoading; + final SuggestionSelectionCallback? onSuggestionSelected; + final SuggestionsCallback? suggestionsCallback; + final ItemBuilder? itemBuilder; + final CupertinoSuggestionsBoxDecoration? decoration; + final Duration? debounceDuration; + final WidgetBuilder? loadingBuilder; + final WidgetBuilder? noItemsFoundBuilder; + final ErrorBuilder? errorBuilder; + final AnimationTransitionBuilder? transitionBuilder; + final Duration? animationDuration; + final double? animationStart; + final AxisDirection? direction; + final bool? hideOnLoading; + final bool? hideOnEmpty; + final bool? hideOnError; + final bool? keepSuggestionsOnLoading; _SuggestionsList({ - @required this.suggestionsBox, + required this.suggestionsBox, this.controller, this.getImmediateSuggestions: false, this.onSuggestionSelected, @@ -813,19 +802,19 @@ class _SuggestionsList extends StatefulWidget { _SuggestionsListState createState() => _SuggestionsListState(); } -class _SuggestionsListState extends State<_SuggestionsList> +class _SuggestionsListState extends State<_SuggestionsList> with SingleTickerProviderStateMixin { - Iterable _suggestions; - bool _suggestionsValid; - VoidCallback _controllerListener; - Timer _debounceTimer; - bool _isLoading, _isQueued; - Object _error; - AnimationController _animationController; - String _lastTextValue; + Iterable? _suggestions; + late bool _suggestionsValid; + late VoidCallback _controllerListener; + Timer? _debounceTimer; + bool? _isLoading, _isQueued; + Object? _error; + AnimationController? _animationController; + String? _lastTextValue; @override - void didUpdateWidget(_SuggestionsList oldWidget) { + void didUpdateWidget(_SuggestionsList oldWidget) { super.didUpdateWidget(oldWidget); _getSuggestions(); } @@ -848,7 +837,7 @@ class _SuggestionsListState extends State<_SuggestionsList> this._suggestionsValid = false; this._isLoading = false; this._isQueued = false; - this._lastTextValue = widget.controller.text; + this._lastTextValue = widget.controller!.text; if (widget.getImmediateSuggestions) { this._getSuggestions(); @@ -857,27 +846,27 @@ class _SuggestionsListState extends State<_SuggestionsList> this._controllerListener = () { // If we came here because of a change in selected text, not because of // actual change in text - if (widget.controller.text == this._lastTextValue) return; + if (widget.controller!.text == this._lastTextValue) return; - this._lastTextValue = widget.controller.text; + this._lastTextValue = widget.controller!.text; this._debounceTimer?.cancel(); - this._debounceTimer = Timer(widget.debounceDuration, () async { - if (this._debounceTimer.isActive) return; - if (_isLoading) { + this._debounceTimer = Timer(widget.debounceDuration!, () async { + if (this._debounceTimer!.isActive) return; + if (_isLoading!) { _isQueued = true; return; } await this.invalidateSuggestions(); - while (_isQueued) { + while (_isQueued!) { _isQueued = false; await this.invalidateSuggestions(); } }); }; - widget.controller.addListener(this._controllerListener); + widget.controller!.addListener(this._controllerListener); } Future invalidateSuggestions() async { @@ -891,17 +880,18 @@ class _SuggestionsListState extends State<_SuggestionsList> if (mounted) { setState(() { - this._animationController.forward(from: 1.0); + this._animationController!.forward(from: 1.0); this._isLoading = true; this._error = null; }); - Iterable suggestions = []; - Object error; + Iterable suggestions = []; + Object? error; try { - suggestions = await widget.suggestionsCallback(widget.controller.text); + suggestions = + await widget.suggestionsCallback!(widget.controller!.text); } catch (e) { error = e; } @@ -909,11 +899,11 @@ class _SuggestionsListState extends State<_SuggestionsList> if (this.mounted) { // if it wasn't removed in the meantime setState(() { - double animationStart = widget.animationStart; - if (error != null || suggestions == null || suggestions.isEmpty) { + double? animationStart = widget.animationStart; + if (error != null || suggestions.isEmpty) { animationStart = 1.0; } - this._animationController.forward(from: animationStart); + this._animationController!.forward(from: animationStart); this._error = error; this._isLoading = false; @@ -925,7 +915,7 @@ class _SuggestionsListState extends State<_SuggestionsList> @override void dispose() { - _animationController.dispose(); + _animationController!.dispose(); // when this suggestions list is closed, text changes are no longer being listened for widget.controller?.removeListener(this._controllerListener); super.dispose(); @@ -937,20 +927,20 @@ class _SuggestionsListState extends State<_SuggestionsList> return Container(); Widget child; - if (this._isLoading) { - if (widget.hideOnLoading) { + if (this._isLoading!) { + if (widget.hideOnLoading!) { child = Container(height: 0); } else { child = createLoadingWidget(); } } else if (this._error != null) { - if (widget.hideOnError) { + if (widget.hideOnError!) { child = Container(height: 0); } else { child = createErrorWidget(); } - } else if (this._suggestions.isEmpty) { - if (widget.hideOnEmpty) { + } else if (this._suggestions!.isEmpty) { + if (widget.hideOnEmpty!) { child = Container(height: 0); } else { child = createNoItemsFoundWidget(); @@ -960,24 +950,25 @@ class _SuggestionsListState extends State<_SuggestionsList> } var animationChild = widget.transitionBuilder != null - ? widget.transitionBuilder(context, child, this._animationController) + ? widget.transitionBuilder!(context, child, this._animationController) : SizeTransition( axisAlignment: -1.0, sizeFactor: CurvedAnimation( - parent: this._animationController, curve: Curves.fastOutSlowIn), + parent: this._animationController!, + curve: Curves.fastOutSlowIn), child: child, ); BoxConstraints constraints; - if (widget.decoration.constraints == null) { + if (widget.decoration!.constraints == null) { constraints = BoxConstraints( - maxHeight: widget.suggestionsBox.maxHeight, + maxHeight: widget.suggestionsBox!.maxHeight, ); } else { - double maxHeight = min(widget.decoration.constraints.maxHeight, - widget.suggestionsBox.maxHeight); - constraints = widget.decoration.constraints.copyWith( - minHeight: min(widget.decoration.constraints.minHeight, maxHeight), + double maxHeight = min(widget.decoration!.constraints!.maxHeight, + widget.suggestionsBox!.maxHeight); + constraints = widget.decoration!.constraints!.copyWith( + minHeight: min(widget.decoration!.constraints!.minHeight, maxHeight), maxHeight: maxHeight, ); } @@ -991,15 +982,15 @@ class _SuggestionsListState extends State<_SuggestionsList> Widget createLoadingWidget() { Widget child; - if (widget.keepSuggestionsOnLoading && this._suggestions != null) { - if (this._suggestions.isEmpty) { + if (widget.keepSuggestionsOnLoading! && this._suggestions != null) { + if (this._suggestions!.isEmpty) { child = createNoItemsFoundWidget(); } else { child = createSuggestionsWidget(); } } else { child = widget.loadingBuilder != null - ? widget.loadingBuilder(context) + ? widget.loadingBuilder!(context) : Container( decoration: BoxDecoration( color: CupertinoColors.white, @@ -1023,7 +1014,7 @@ class _SuggestionsListState extends State<_SuggestionsList> Widget createErrorWidget() { return widget.errorBuilder != null - ? widget.errorBuilder(context, this._error) + ? widget.errorBuilder!(context, this._error) : Container( decoration: BoxDecoration( color: CupertinoColors.white, @@ -1048,7 +1039,7 @@ class _SuggestionsListState extends State<_SuggestionsList> Widget createNoItemsFoundWidget() { return widget.noItemsFoundBuilder != null - ? widget.noItemsFoundBuilder(context) + ? widget.noItemsFoundBuilder!(context) : Container( decoration: BoxDecoration( color: CupertinoColors.white, @@ -1074,39 +1065,39 @@ class _SuggestionsListState extends State<_SuggestionsList> Widget createSuggestionsWidget() { Widget child = Container( decoration: BoxDecoration( - color: widget.decoration.color != null - ? widget.decoration.color + color: widget.decoration!.color != null + ? widget.decoration!.color : CupertinoColors.white, - border: widget.decoration.border != null - ? widget.decoration.border + border: widget.decoration!.border != null + ? widget.decoration!.border : Border.all( color: CupertinoColors.extraLightBackgroundGray, width: 1.0, ), - borderRadius: widget.decoration.borderRadius != null - ? widget.decoration.borderRadius + borderRadius: widget.decoration!.borderRadius != null + ? widget.decoration!.borderRadius : null, ), child: ListView( padding: EdgeInsets.zero, primary: false, shrinkWrap: true, - reverse: widget.suggestionsBox.direction == AxisDirection.down + reverse: widget.suggestionsBox!.direction == AxisDirection.down ? false : true, // reverses the list to start at the bottom - children: this._suggestions.map((T suggestion) { + children: this._suggestions!.map((T? suggestion) { return GestureDetector( behavior: HitTestBehavior.translucent, - child: widget.itemBuilder(context, suggestion), + child: widget.itemBuilder!(context, suggestion), onTap: () { - widget.onSuggestionSelected(suggestion); + widget.onSuggestionSelected!(suggestion); }, ); }).toList(), ), ); - if (widget.decoration.hasScrollbar) { + if (widget.decoration!.hasScrollbar) { child = CupertinoScrollbar(child: child); } @@ -1121,10 +1112,10 @@ class CupertinoSuggestionsBoxDecoration { final bool hasScrollbar; /// The constraints to be applied to the suggestions box - final BoxConstraints constraints; - final Color color; - final BoxBorder border; - final BorderRadiusGeometry borderRadius; + final BoxConstraints? constraints; + final Color? color; + final BoxBorder? border; + final BorderRadiusGeometry? borderRadius; /// Adds an offset to the suggestions box final double offsetX; @@ -1143,39 +1134,39 @@ class CupertinoSuggestionsBoxDecoration { /// property to configure the displayed text field. See [documentation](https://docs.flutter.io/flutter/cupertino/CupertinoTextField-class.html) /// for more information on properties. class CupertinoTextFieldConfiguration { - final TextEditingController controller; - final FocusNode focusNode; + final TextEditingController? controller; + final FocusNode? focusNode; final BoxDecoration decoration; final EdgeInsetsGeometry padding; - final String placeholder; - final Widget prefix; + final String? placeholder; + final Widget? prefix; final OverlayVisibilityMode prefixMode; - final Widget suffix; + final Widget? suffix; final OverlayVisibilityMode suffixMode; final OverlayVisibilityMode clearButtonMode; - final TextInputType keyboardType; - final TextInputAction textInputAction; + final TextInputType? keyboardType; + final TextInputAction? textInputAction; final TextCapitalization textCapitalization; - final TextStyle style; + final TextStyle? style; final TextAlign textAlign; final bool autofocus; final bool obscureText; final bool autocorrect; final int maxLines; - final int minLines; - final int maxLength; + final int? minLines; + final int? maxLength; final bool maxLengthEnforced; - final ValueChanged onChanged; - final VoidCallback onEditingComplete; - final GestureTapCallback onTap; - final ValueChanged onSubmitted; - final List inputFormatters; + final ValueChanged? onChanged; + final VoidCallback? onEditingComplete; + final GestureTapCallback? onTap; + final ValueChanged? onSubmitted; + final List? inputFormatters; final bool enabled; final bool enableSuggestions; final double cursorWidth; final Radius cursorRadius; - final Color cursorColor; - final Brightness keyboardAppearance; + final Color? cursorColor; + final Brightness? keyboardAppearance; final EdgeInsets scrollPadding; final bool enableInteractiveSelection; @@ -1220,40 +1211,41 @@ class CupertinoTextFieldConfiguration { /// Copies the [CupertinoTextFieldConfiguration] and only changes the specified properties copyWith({ - TextEditingController controller, - FocusNode focusNode, - BoxDecoration decoration, - EdgeInsetsGeometry padding, - String placeholder, - Widget prefix, - OverlayVisibilityMode prefixMode, - Widget suffix, - OverlayVisibilityMode suffixMode, - OverlayVisibilityMode clearButtonMode, - TextInputType keyboardType, - TextInputAction textInputAction, - TextCapitalization textCapitalization, - TextStyle style, - TextAlign textAlign, - bool autofocus, - bool obscureText, - bool autocorrect, - int maxLines, - int minLines, - int maxLength, - bool maxLengthEnforced, - ValueChanged onChanged, - VoidCallback onEditingComplete, - GestureTapCallback onTap, - ValueChanged onSubmitted, - List inputFormatters, - bool enabled, - double cursorWidth, - Radius cursorRadius, - Color cursorColor, - Brightness keyboardAppearance, - EdgeInsets scrollPadding, - bool enableInteractiveSelection, + TextEditingController? controller, + FocusNode? focusNode, + BoxDecoration? decoration, + EdgeInsetsGeometry? padding, + String? placeholder, + Widget? prefix, + OverlayVisibilityMode? prefixMode, + Widget? suffix, + OverlayVisibilityMode? suffixMode, + OverlayVisibilityMode? clearButtonMode, + TextInputType? keyboardType, + TextInputAction? textInputAction, + TextCapitalization? textCapitalization, + TextStyle? style, + TextAlign? textAlign, + bool? autofocus, + bool? obscureText, + bool? autocorrect, + int? maxLines, + int? minLines, + int? maxLength, + bool? maxLengthEnforced, + ValueChanged? onChanged, + VoidCallback? onEditingComplete, + GestureTapCallback? onTap, + ValueChanged? onSubmitted, + List? inputFormatters, + bool? enabled, + bool? enableSuggestions, + double? cursorWidth, + Radius? cursorRadius, + Color? cursorColor, + Brightness? keyboardAppearance, + EdgeInsets? scrollPadding, + bool? enableInteractiveSelection, }) { return CupertinoTextFieldConfiguration( controller: controller ?? this.controller, @@ -1304,7 +1296,7 @@ class _CupertinoSuggestionsBox { final AxisDirection desiredDirection; final bool autoFlipDirection; - OverlayEntry _overlayEntry; + OverlayEntry? _overlayEntry; AxisDirection direction; bool isOpened = false; @@ -1312,7 +1304,7 @@ class _CupertinoSuggestionsBox { double maxHeight = 300.0; double textBoxWidth = 100.0; double textBoxHeight = 100.0; - double directionUpOffset; + late double directionUpOffset; _CupertinoSuggestionsBox(this.context, this.direction, this.autoFlipDirection) : desiredDirection = direction; @@ -1320,14 +1312,14 @@ class _CupertinoSuggestionsBox { void open() { if (this.isOpened) return; assert(this._overlayEntry != null); - Overlay.of(context).insert(this._overlayEntry); + Overlay.of(context)!.insert(this._overlayEntry!); this.isOpened = true; } void close() { if (!this.isOpened) return; assert(this._overlayEntry != null); - this._overlayEntry.remove(); + this._overlayEntry!.remove(); this.isOpened = false; } @@ -1339,8 +1331,8 @@ class _CupertinoSuggestionsBox { } } - MediaQuery _findRootMediaQuery() { - MediaQuery rootMediaQuery; + MediaQuery? _findRootMediaQuery() { + MediaQuery? rootMediaQuery; context.visitAncestorElements((element) { if (element.widget is MediaQuery) { rootMediaQuery = element.widget as MediaQuery; @@ -1357,7 +1349,7 @@ class _CupertinoSuggestionsBox { // initial viewInsets which are before the keyboard is toggled EdgeInsets initial = MediaQuery.of(context).viewInsets; // initial MediaQuery for orientation change - MediaQuery initialRootMediaQuery = _findRootMediaQuery(); + MediaQuery? initialRootMediaQuery = _findRootMediaQuery(); int timer = 0; // viewInsets or MediaQuery have changed once keyboard has toggled or orientation has changed @@ -1382,7 +1374,7 @@ class _CupertinoSuggestionsBox { // user may have closed the widget with the keyboard still open if (widgetMounted) { _adjustMaxHeightAndOrientation(); - _overlayEntry.markNeedsBuild(); + _overlayEntry!.markNeedsBuild(); } } @@ -1391,7 +1383,7 @@ class _CupertinoSuggestionsBox { void _adjustMaxHeightAndOrientation() { CupertinoTypeAheadField widget = context.widget as CupertinoTypeAheadField; - RenderBox box = context.findRenderObject(); + RenderBox box = context.findRenderObject() as RenderBox; textBoxWidth = box.size.width; textBoxHeight = box.size.height; @@ -1404,7 +1396,7 @@ class _CupertinoSuggestionsBox { // we need to find the root MediaQuery for the unsafe area height // we cannot use BuildContext.ancestorWidgetOfExactType because // widgets like SafeArea creates a new MediaQuery with the padding removed - MediaQuery rootMediaQuery = _findRootMediaQuery(); + MediaQuery rootMediaQuery = _findRootMediaQuery()!; // height of keyboard double keyboardHeight = rootMediaQuery.data.viewInsets.bottom; @@ -1456,9 +1448,8 @@ class _CupertinoSuggestionsBox { double textBoxAbsY) { // unsafe area, ie: iPhone X 'home button' // keyboardHeight includes unsafeAreaHeight, if keyboard is showing, set to 0 - double unsafeAreaHeight = keyboardHeight == 0 && rootMediaQuery != null - ? rootMediaQuery.data.padding.bottom - : 0; + double unsafeAreaHeight = + keyboardHeight == 0 ? rootMediaQuery.data.padding.bottom : 0; return windowHeight - keyboardHeight - @@ -1504,22 +1495,22 @@ class _CupertinoSuggestionsBox { /// Supply an instance of this class to the [TypeAhead.suggestionsBoxController] /// property to manually control the suggestions box class CupertinoSuggestionsBoxController { - _CupertinoSuggestionsBox _suggestionsBox; - FocusNode _effectiveFocusNode; + _CupertinoSuggestionsBox? _suggestionsBox; + FocusNode? _effectiveFocusNode; /// Opens the suggestions box void open() { - _effectiveFocusNode.requestFocus(); + _effectiveFocusNode!.requestFocus(); } /// Closes the suggestions box void close() { - _effectiveFocusNode.unfocus(); + _effectiveFocusNode!.unfocus(); } /// Opens the suggestions box if closed and vice-versa void toggle() { - if (_suggestionsBox.isOpened) { + if (_suggestionsBox!.isOpened) { close(); } else { open(); @@ -1528,6 +1519,6 @@ class CupertinoSuggestionsBoxController { /// Recalculates the height of the suggestions box void resize() { - _suggestionsBox.resize(); + _suggestionsBox!.resize(); } } diff --git a/lib/flutter_typeahead.dart b/lib/flutter_typeahead.dart index 16da0873..f6f7f90d 100644 --- a/lib/flutter_typeahead.dart +++ b/lib/flutter_typeahead.dart @@ -236,10 +236,10 @@ import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; typedef FutureOr> SuggestionsCallback(String pattern); typedef Widget ItemBuilder(BuildContext context, T itemData); typedef void SuggestionSelectionCallback(T suggestion); -typedef Widget ErrorBuilder(BuildContext context, Object error); +typedef Widget ErrorBuilder(BuildContext context, Object? error); typedef AnimationTransitionBuilder( - BuildContext context, Widget child, AnimationController controller); + BuildContext context, Widget child, AnimationController? controller); /// A [FormField](https://docs.flutter.io/flutter/widgets/FormField-class.html) /// implementation of [TypeAheadField], that allows the value to be saved, @@ -256,8 +256,8 @@ class TypeAheadFormField extends FormField { /// Creates a [TypeAheadFormField] TypeAheadFormField( - {Key key, - String initialValue, + {Key? key, + String? initialValue, bool getImmediateSuggestions: false, @Deprecated('Use autovalidateMode parameter which provides more specific ' 'behavior related to auto validation. ' @@ -265,24 +265,21 @@ class TypeAheadFormField extends FormField { bool autovalidate: false, bool enabled: true, AutovalidateMode autovalidateMode: AutovalidateMode.disabled, - FormFieldSetter onSaved, - FormFieldValidator validator, - ErrorBuilder errorBuilder, - WidgetBuilder noItemsFoundBuilder, - WidgetBuilder loadingBuilder, + FormFieldSetter? onSaved, + FormFieldValidator? validator, + ErrorBuilder? errorBuilder, + WidgetBuilder? noItemsFoundBuilder, + WidgetBuilder? loadingBuilder, Duration debounceDuration: const Duration(milliseconds: 300), SuggestionsBoxDecoration suggestionsBoxDecoration: const SuggestionsBoxDecoration(), - SuggestionsBoxController suggestionsBoxController, - @required - SuggestionSelectionCallback onSuggestionSelected, - @required - ItemBuilder itemBuilder, - @required - SuggestionsCallback suggestionsCallback, + SuggestionsBoxController? suggestionsBoxController, + required SuggestionSelectionCallback onSuggestionSelected, + required ItemBuilder itemBuilder, + required SuggestionsCallback suggestionsCallback, double suggestionsBoxVerticalOffset: 5.0, this.textFieldConfiguration: const TextFieldConfiguration(), - AnimationTransitionBuilder transitionBuilder, + AnimationTransitionBuilder? transitionBuilder, Duration animationDuration: const Duration(milliseconds: 500), double animationStart: 0.25, AxisDirection direction: AxisDirection.down, @@ -301,12 +298,13 @@ class TypeAheadFormField extends FormField { onSaved: onSaved, validator: validator, initialValue: textFieldConfiguration.controller != null - ? textFieldConfiguration.controller.text + ? textFieldConfiguration.controller!.text : (initialValue ?? ''), enabled: enabled, autovalidateMode: autovalidateMode, builder: (FormFieldState field) { - final _TypeAheadFormFieldState state = field; + final _TypeAheadFormFieldState state = + field as _TypeAheadFormFieldState; return TypeAheadField( getImmediateSuggestions: getImmediateSuggestions, @@ -349,13 +347,13 @@ class TypeAheadFormField extends FormField { } class _TypeAheadFormFieldState extends FormFieldState { - TextEditingController _controller; + TextEditingController? _controller; - TextEditingController get _effectiveController => + TextEditingController? get _effectiveController => widget.textFieldConfiguration.controller ?? _controller; @override - TypeAheadFormField get widget => super.widget; + TypeAheadFormField get widget => super.widget as TypeAheadFormField; @override void initState() { @@ -363,7 +361,7 @@ class _TypeAheadFormFieldState extends FormFieldState { if (widget.textFieldConfiguration.controller == null) { _controller = TextEditingController(text: widget.initialValue); } else { - widget.textFieldConfiguration.controller + widget.textFieldConfiguration.controller! .addListener(_handleControllerChanged); } } @@ -381,9 +379,9 @@ class _TypeAheadFormFieldState extends FormFieldState { if (oldWidget.textFieldConfiguration.controller != null && widget.textFieldConfiguration.controller == null) _controller = TextEditingController.fromValue( - oldWidget.textFieldConfiguration.controller.value); + oldWidget.textFieldConfiguration.controller!.value); if (widget.textFieldConfiguration.controller != null) { - setValue(widget.textFieldConfiguration.controller.text); + setValue(widget.textFieldConfiguration.controller!.text); if (oldWidget.textFieldConfiguration.controller == null) _controller = null; } @@ -401,7 +399,7 @@ class _TypeAheadFormFieldState extends FormFieldState { void reset() { super.reset(); setState(() { - _effectiveController.text = widget.initialValue; + _effectiveController!.text = widget.initialValue!; }); } @@ -413,8 +411,8 @@ class _TypeAheadFormFieldState extends FormFieldState { // notifications for changes originating from within this class -- for // example, the reset() method. In such cases, the FormField value will // already have been set. - if (_effectiveController.text != value) - didChange(_effectiveController.text); + if (_effectiveController!.text != value) + didChange(_effectiveController!.text); } } @@ -494,7 +492,7 @@ class TypeAheadField extends StatefulWidget { /// Used to control the `_SuggestionsBox`. Allows manual control to /// open, close, toggle, or resize the `_SuggestionsBox`. - final SuggestionsBoxController suggestionsBoxController; + final SuggestionsBoxController? suggestionsBoxController; /// The duration to wait after the user stops typing before calling /// [suggestionsCallback] @@ -516,7 +514,7 @@ class TypeAheadField extends StatefulWidget { /// ``` /// /// If not specified, a [CircularProgressIndicator](https://docs.flutter.io/flutter/material/CircularProgressIndicator-class.html) is shown - final WidgetBuilder loadingBuilder; + final WidgetBuilder? loadingBuilder; /// Called when [suggestionsCallback] returns an empty array. /// @@ -530,7 +528,7 @@ class TypeAheadField extends StatefulWidget { /// ``` /// /// If not specified, a simple text is shown - final WidgetBuilder noItemsFoundBuilder; + final WidgetBuilder? noItemsFoundBuilder; /// Called when [suggestionsCallback] throws an exception. /// @@ -544,7 +542,7 @@ class TypeAheadField extends StatefulWidget { /// ``` /// /// If not specified, the error is shown in [ThemeData.errorColor](https://docs.flutter.io/flutter/material/ThemeData/errorColor.html) - final ErrorBuilder errorBuilder; + final ErrorBuilder? errorBuilder; /// Called to display animations when [suggestionsCallback] returns suggestions /// @@ -570,7 +568,7 @@ class TypeAheadField extends StatefulWidget { /// To fully remove the animation, just return `suggestionsBox` /// /// If not specified, a [SizeTransition](https://docs.flutter.io/flutter/widgets/SizeTransition-class.html) is shown. - final AnimationTransitionBuilder transitionBuilder; + final AnimationTransitionBuilder? transitionBuilder; /// The duration that [transitionBuilder] animation takes. /// @@ -672,10 +670,10 @@ class TypeAheadField extends StatefulWidget { /// Creates a [TypeAheadField] TypeAheadField( - {Key key, - @required this.suggestionsCallback, - @required this.itemBuilder, - @required this.onSuggestionSelected, + {Key? key, + required this.suggestionsCallback, + required this.itemBuilder, + required this.onSuggestionSelected, this.textFieldConfiguration: const TextFieldConfiguration(), this.suggestionsBoxDecoration: const SuggestionsBoxDecoration(), this.debounceDuration: const Duration(milliseconds: 300), @@ -697,17 +695,7 @@ class TypeAheadField extends StatefulWidget { this.keepSuggestionsOnSuggestionSelected: false, this.autoFlipDirection: false, this.hideKeyboard: false}) - : assert(suggestionsCallback != null), - assert(itemBuilder != null), - assert(onSuggestionSelected != null), - assert(animationStart != null && - animationStart >= 0.0 && - animationStart <= 1.0), - assert(animationDuration != null), - assert(debounceDuration != null), - assert(textFieldConfiguration != null), - assert(suggestionsBoxDecoration != null), - assert(suggestionsBoxVerticalOffset != null), + : assert(animationStart >= 0.0 && animationStart <= 1.0), assert( direction == AxisDirection.down || direction == AxisDirection.up), super(key: key); @@ -718,43 +706,43 @@ class TypeAheadField extends StatefulWidget { class _TypeAheadFieldState extends State> with WidgetsBindingObserver { - FocusNode _focusNode; - TextEditingController _textEditingController; - _SuggestionsBox _suggestionsBox; + FocusNode? _focusNode; + TextEditingController? _textEditingController; + _SuggestionsBox? _suggestionsBox; - TextEditingController get _effectiveController => + TextEditingController? get _effectiveController => widget.textFieldConfiguration.controller ?? _textEditingController; - FocusNode get _effectiveFocusNode => + FocusNode? get _effectiveFocusNode => widget.textFieldConfiguration.focusNode ?? _focusNode; - VoidCallback _focusNodeListener; + late VoidCallback _focusNodeListener; final LayerLink _layerLink = LayerLink(); // Timer that resizes the suggestion box on each tick. Only active when the user is scrolling. - Timer _resizeOnScrollTimer; + Timer? _resizeOnScrollTimer; // The rate at which the suggestion box will resize when the user is scrolling final Duration _resizeOnScrollRefreshRate = const Duration(milliseconds: 500); // Will have a value if the typeahead is inside a scrollable widget - ScrollPosition _scrollPosition; + ScrollPosition? _scrollPosition; // Keyboard detection final Stream _keyboardVisibility = KeyboardVisibilityController().onChange; - StreamSubscription _keyboardVisibilitySubscription; + late StreamSubscription _keyboardVisibilitySubscription; @override void didChangeMetrics() { // Catch keyboard event and orientation change; resize suggestions list - this._suggestionsBox.onChangeMetrics(); + this._suggestionsBox!.onChangeMetrics(); } @override void dispose() { - this._suggestionsBox.close(); - this._suggestionsBox.widgetMounted = false; - WidgetsBinding.instance.removeObserver(this); + this._suggestionsBox!.close(); + this._suggestionsBox!.widgetMounted = false; + WidgetsBinding.instance!.removeObserver(this); _keyboardVisibilitySubscription.cancel(); - _effectiveFocusNode.removeListener(_focusNodeListener); + _effectiveFocusNode!.removeListener(_focusNodeListener); _focusNode?.dispose(); _resizeOnScrollTimer?.cancel(); _scrollPosition?.removeListener(_scrollResizeListener); @@ -765,7 +753,7 @@ class _TypeAheadFieldState extends State> @override void initState() { super.initState(); - WidgetsBinding.instance.addObserver(this); + WidgetsBinding.instance!.addObserver(this); if (widget.textFieldConfiguration.controller == null) { this._textEditingController = TextEditingController(); @@ -782,32 +770,32 @@ class _TypeAheadFieldState extends State> this._effectiveFocusNode; this._focusNodeListener = () { - if (_effectiveFocusNode.hasFocus) { - this._suggestionsBox.open(); + if (_effectiveFocusNode!.hasFocus) { + this._suggestionsBox!.open(); } else { - this._suggestionsBox.close(); + this._suggestionsBox!.close(); } }; - this._effectiveFocusNode.addListener(_focusNodeListener); + this._effectiveFocusNode!.addListener(_focusNodeListener); // hide suggestions box on keyboard closed this._keyboardVisibilitySubscription = _keyboardVisibility.listen((bool isVisible) { if (widget.hideSuggestionsOnKeyboardHide && !isVisible) { - _effectiveFocusNode.unfocus(); + _effectiveFocusNode!.unfocus(); } }); - WidgetsBinding.instance.addPostFrameCallback((duration) { + WidgetsBinding.instance!.addPostFrameCallback((duration) { if (mounted) { this._initOverlayEntry(); // calculate initial suggestions list size - this._suggestionsBox.resize(); + this._suggestionsBox!.resize(); // in case we already missed the focus event - if (this._effectiveFocusNode.hasFocus) { - this._suggestionsBox.open(); + if (this._effectiveFocusNode!.hasFocus) { + this._suggestionsBox!.open(); } } }); @@ -816,33 +804,33 @@ class _TypeAheadFieldState extends State> @override void didChangeDependencies() { super.didChangeDependencies(); - ScrollableState scrollableState = Scrollable.of(context); + ScrollableState? scrollableState = Scrollable.of(context); if (scrollableState != null) { // The TypeAheadField is inside a scrollable widget _scrollPosition = scrollableState.position; - _scrollPosition.removeListener(_scrollResizeListener); - _scrollPosition.isScrollingNotifier.addListener(_scrollResizeListener); + _scrollPosition!.removeListener(_scrollResizeListener); + _scrollPosition!.isScrollingNotifier.addListener(_scrollResizeListener); } } void _scrollResizeListener() { - bool isScrolling = _scrollPosition.isScrollingNotifier.value; + bool isScrolling = _scrollPosition!.isScrollingNotifier.value; _resizeOnScrollTimer?.cancel(); if (isScrolling) { // Scroll started _resizeOnScrollTimer = Timer.periodic(_resizeOnScrollRefreshRate, (timer) { - _suggestionsBox.resize(); + _suggestionsBox!.resize(); }); } else { // Scroll finished - _suggestionsBox.resize(); + _suggestionsBox!.resize(); } } void _initOverlayEntry() { - this._suggestionsBox._overlayEntry = OverlayEntry(builder: (context) { + this._suggestionsBox!._overlayEntry = OverlayEntry(builder: (context) { final suggestionsList = _SuggestionsList( suggestionsBox: _suggestionsBox, decoration: widget.suggestionsBoxDecoration, @@ -858,35 +846,35 @@ class _TypeAheadFieldState extends State> getImmediateSuggestions: widget.getImmediateSuggestions, onSuggestionSelected: (T selection) { if (!widget.keepSuggestionsOnSuggestionSelected) { - this._effectiveFocusNode.unfocus(); - this._suggestionsBox.close(); + this._effectiveFocusNode!.unfocus(); + this._suggestionsBox!.close(); } widget.onSuggestionSelected(selection); }, itemBuilder: widget.itemBuilder, - direction: _suggestionsBox.direction, + direction: _suggestionsBox!.direction, hideOnLoading: widget.hideOnLoading, hideOnEmpty: widget.hideOnEmpty, hideOnError: widget.hideOnError, keepSuggestionsOnLoading: widget.keepSuggestionsOnLoading, ); - double w = _suggestionsBox.textBoxWidth; + double w = _suggestionsBox!.textBoxWidth; if (widget.suggestionsBoxDecoration.constraints != null) { - if (widget.suggestionsBoxDecoration.constraints.minWidth != 0.0 && - widget.suggestionsBoxDecoration.constraints.maxWidth != + if (widget.suggestionsBoxDecoration.constraints!.minWidth != 0.0 && + widget.suggestionsBoxDecoration.constraints!.maxWidth != double.infinity) { - w = (widget.suggestionsBoxDecoration.constraints.minWidth + - widget.suggestionsBoxDecoration.constraints.maxWidth) / + w = (widget.suggestionsBoxDecoration.constraints!.minWidth + + widget.suggestionsBoxDecoration.constraints!.maxWidth) / 2; - } else if (widget.suggestionsBoxDecoration.constraints.minWidth != + } else if (widget.suggestionsBoxDecoration.constraints!.minWidth != 0.0 && - widget.suggestionsBoxDecoration.constraints.minWidth > w) { - w = widget.suggestionsBoxDecoration.constraints.minWidth; - } else if (widget.suggestionsBoxDecoration.constraints.maxWidth != + widget.suggestionsBoxDecoration.constraints!.minWidth > w) { + w = widget.suggestionsBoxDecoration.constraints!.minWidth; + } else if (widget.suggestionsBoxDecoration.constraints!.maxWidth != double.infinity && - widget.suggestionsBoxDecoration.constraints.maxWidth < w) { - w = widget.suggestionsBoxDecoration.constraints.maxWidth; + widget.suggestionsBoxDecoration.constraints!.maxWidth < w) { + w = widget.suggestionsBoxDecoration.constraints!.maxWidth; } } @@ -897,11 +885,11 @@ class _TypeAheadFieldState extends State> showWhenUnlinked: false, offset: Offset( widget.suggestionsBoxDecoration.offsetX, - _suggestionsBox.direction == AxisDirection.down - ? _suggestionsBox.textBoxHeight + + _suggestionsBox!.direction == AxisDirection.down + ? _suggestionsBox!.textBoxHeight + widget.suggestionsBoxVerticalOffset - : _suggestionsBox.directionUpOffset), - child: _suggestionsBox.direction == AxisDirection.down + : _suggestionsBox!.directionUpOffset), + child: _suggestionsBox!.direction == AxisDirection.down ? suggestionsList : FractionalTranslation( translation: @@ -954,28 +942,28 @@ class _TypeAheadFieldState extends State> } class _SuggestionsList extends StatefulWidget { - final _SuggestionsBox suggestionsBox; - final TextEditingController controller; + final _SuggestionsBox? suggestionsBox; + final TextEditingController? controller; final bool getImmediateSuggestions; - final SuggestionSelectionCallback onSuggestionSelected; - final SuggestionsCallback suggestionsCallback; - final ItemBuilder itemBuilder; - final SuggestionsBoxDecoration decoration; - final Duration debounceDuration; - final WidgetBuilder loadingBuilder; - final WidgetBuilder noItemsFoundBuilder; - final ErrorBuilder errorBuilder; - final AnimationTransitionBuilder transitionBuilder; - final Duration animationDuration; - final double animationStart; - final AxisDirection direction; - final bool hideOnLoading; - final bool hideOnEmpty; - final bool hideOnError; - final bool keepSuggestionsOnLoading; + final SuggestionSelectionCallback? onSuggestionSelected; + final SuggestionsCallback? suggestionsCallback; + final ItemBuilder? itemBuilder; + final SuggestionsBoxDecoration? decoration; + final Duration? debounceDuration; + final WidgetBuilder? loadingBuilder; + final WidgetBuilder? noItemsFoundBuilder; + final ErrorBuilder? errorBuilder; + final AnimationTransitionBuilder? transitionBuilder; + final Duration? animationDuration; + final double? animationStart; + final AxisDirection? direction; + final bool? hideOnLoading; + final bool? hideOnEmpty; + final bool? hideOnError; + final bool? keepSuggestionsOnLoading; _SuggestionsList({ - @required this.suggestionsBox, + required this.suggestionsBox, this.controller, this.getImmediateSuggestions: false, this.onSuggestionSelected, @@ -1000,35 +988,35 @@ class _SuggestionsList extends StatefulWidget { _SuggestionsListState createState() => _SuggestionsListState(); } -class _SuggestionsListState extends State<_SuggestionsList> +class _SuggestionsListState extends State<_SuggestionsList> with SingleTickerProviderStateMixin { - Iterable _suggestions; - bool _suggestionsValid; - VoidCallback _controllerListener; - Timer _debounceTimer; - bool _isLoading, _isQueued; - Object _error; - AnimationController _animationController; - String _lastTextValue; + Iterable? _suggestions; + late bool _suggestionsValid; + late VoidCallback _controllerListener; + Timer? _debounceTimer; + bool? _isLoading, _isQueued; + Object? _error; + AnimationController? _animationController; + String? _lastTextValue; _SuggestionsListState() { this._controllerListener = () { // If we came here because of a change in selected text, not because of // actual change in text - if (widget.controller.text == this._lastTextValue) return; + if (widget.controller!.text == this._lastTextValue) return; - this._lastTextValue = widget.controller.text; + this._lastTextValue = widget.controller!.text; this._debounceTimer?.cancel(); - this._debounceTimer = Timer(widget.debounceDuration, () async { - if (this._debounceTimer.isActive) return; - if (_isLoading) { + this._debounceTimer = Timer(widget.debounceDuration!, () async { + if (this._debounceTimer!.isActive) return; + if (_isLoading!) { _isQueued = true; return; } await this.invalidateSuggestions(); - while (_isQueued) { + while (_isQueued!) { _isQueued = false; await this.invalidateSuggestions(); } @@ -1037,9 +1025,9 @@ class _SuggestionsListState extends State<_SuggestionsList> } @override - void didUpdateWidget(_SuggestionsList oldWidget) { + void didUpdateWidget(_SuggestionsList oldWidget) { super.didUpdateWidget(oldWidget); - widget.controller.addListener(this._controllerListener); + widget.controller!.addListener(this._controllerListener); _getSuggestions(); } @@ -1061,13 +1049,13 @@ class _SuggestionsListState extends State<_SuggestionsList> this._suggestionsValid = false; this._isLoading = false; this._isQueued = false; - this._lastTextValue = widget.controller.text; + this._lastTextValue = widget.controller!.text; if (widget.getImmediateSuggestions) { this._getSuggestions(); } - widget.controller.addListener(this._controllerListener); + widget.controller!.addListener(this._controllerListener); } Future invalidateSuggestions() async { @@ -1081,17 +1069,18 @@ class _SuggestionsListState extends State<_SuggestionsList> if (mounted) { setState(() { - this._animationController.forward(from: 1.0); + this._animationController!.forward(from: 1.0); this._isLoading = true; this._error = null; }); - Iterable suggestions = []; - Object error; + Iterable suggestions = []; + Object? error; try { - suggestions = await widget.suggestionsCallback(widget.controller.text); + suggestions = + await widget.suggestionsCallback!(widget.controller!.text); } catch (e) { error = e; } @@ -1099,11 +1088,11 @@ class _SuggestionsListState extends State<_SuggestionsList> if (this.mounted) { // if it wasn't removed in the meantime setState(() { - double animationStart = widget.animationStart; - if (error != null || suggestions == null || suggestions.isEmpty) { + double? animationStart = widget.animationStart; + if (error != null || suggestions.isEmpty) { animationStart = 1.0; } - this._animationController.forward(from: animationStart); + this._animationController!.forward(from: animationStart); this._error = error; this._isLoading = false; @@ -1115,7 +1104,7 @@ class _SuggestionsListState extends State<_SuggestionsList> @override void dispose() { - _animationController.dispose(); + _animationController!.dispose(); super.dispose(); } @@ -1125,20 +1114,20 @@ class _SuggestionsListState extends State<_SuggestionsList> return Container(); Widget child; - if (this._isLoading) { - if (widget.hideOnLoading) { + if (this._isLoading!) { + if (widget.hideOnLoading!) { child = Container(height: 0); } else { child = createLoadingWidget(); } } else if (this._error != null) { - if (widget.hideOnError) { + if (widget.hideOnError!) { child = Container(height: 0); } else { child = createErrorWidget(); } - } else if (this._suggestions.isEmpty) { - if (widget.hideOnEmpty) { + } else if (this._suggestions!.isEmpty) { + if (widget.hideOnEmpty!) { child = Container(height: 0); } else { child = createNoItemsFoundWidget(); @@ -1148,35 +1137,36 @@ class _SuggestionsListState extends State<_SuggestionsList> } var animationChild = widget.transitionBuilder != null - ? widget.transitionBuilder(context, child, this._animationController) + ? widget.transitionBuilder!(context, child, this._animationController) : SizeTransition( axisAlignment: -1.0, sizeFactor: CurvedAnimation( - parent: this._animationController, curve: Curves.fastOutSlowIn), + parent: this._animationController!, + curve: Curves.fastOutSlowIn), child: child, ); BoxConstraints constraints; - if (widget.decoration.constraints == null) { + if (widget.decoration!.constraints == null) { constraints = BoxConstraints( - maxHeight: widget.suggestionsBox.maxHeight, + maxHeight: widget.suggestionsBox!.maxHeight, ); } else { - double maxHeight = min(widget.decoration.constraints.maxHeight, - widget.suggestionsBox.maxHeight); - constraints = widget.decoration.constraints.copyWith( - minHeight: min(widget.decoration.constraints.minHeight, maxHeight), + double maxHeight = min(widget.decoration!.constraints!.maxHeight, + widget.suggestionsBox!.maxHeight); + constraints = widget.decoration!.constraints!.copyWith( + minHeight: min(widget.decoration!.constraints!.minHeight, maxHeight), maxHeight: maxHeight, ); } var container = Material( - elevation: widget.decoration.elevation, - color: widget.decoration.color, - shape: widget.decoration.shape, - borderRadius: widget.decoration.borderRadius, - shadowColor: widget.decoration.shadowColor, - clipBehavior: widget.decoration.clipBehavior, + elevation: widget.decoration!.elevation, + color: widget.decoration!.color, + shape: widget.decoration!.shape, + borderRadius: widget.decoration!.borderRadius, + shadowColor: widget.decoration!.shadowColor, + clipBehavior: widget.decoration!.clipBehavior, child: ConstrainedBox( constraints: constraints, child: animationChild, @@ -1189,15 +1179,15 @@ class _SuggestionsListState extends State<_SuggestionsList> Widget createLoadingWidget() { Widget child; - if (widget.keepSuggestionsOnLoading && this._suggestions != null) { - if (this._suggestions.isEmpty) { + if (widget.keepSuggestionsOnLoading! && this._suggestions != null) { + if (this._suggestions!.isEmpty) { child = createNoItemsFoundWidget(); } else { child = createSuggestionsWidget(); } } else { child = widget.loadingBuilder != null - ? widget.loadingBuilder(context) + ? widget.loadingBuilder!(context) : Align( alignment: Alignment.center, child: Padding( @@ -1212,7 +1202,7 @@ class _SuggestionsListState extends State<_SuggestionsList> Widget createErrorWidget() { return widget.errorBuilder != null - ? widget.errorBuilder(context, this._error) + ? widget.errorBuilder!(context, this._error) : Padding( padding: const EdgeInsets.all(8.0), child: Text( @@ -1224,7 +1214,7 @@ class _SuggestionsListState extends State<_SuggestionsList> Widget createNoItemsFoundWidget() { return widget.noItemsFoundBuilder != null - ? widget.noItemsFoundBuilder(context) + ? widget.noItemsFoundBuilder!(context) : Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text( @@ -1241,20 +1231,20 @@ class _SuggestionsListState extends State<_SuggestionsList> padding: EdgeInsets.zero, primary: false, shrinkWrap: true, - reverse: widget.suggestionsBox.direction == AxisDirection.down + reverse: widget.suggestionsBox!.direction == AxisDirection.down ? false : true, // reverses the list to start at the bottom - children: this._suggestions.map((T suggestion) { + children: this._suggestions!.map((T? suggestion) { return InkWell( - child: widget.itemBuilder(context, suggestion), + child: widget.itemBuilder!(context, suggestion), onTap: () { - widget.onSuggestionSelected(suggestion); + widget.onSuggestionSelected!(suggestion); }, ); }).toList(), ); - if (widget.decoration.hasScrollbar) { + if (widget.decoration!.hasScrollbar) { child = Scrollbar(child: child); } @@ -1274,12 +1264,12 @@ class SuggestionsBoxDecoration { /// The color to paint the suggestions box. /// /// Same as [Material.color](https://docs.flutter.io/flutter/material/Material/color.html) - final Color color; + final Color? color; /// Defines the material's shape as well its shadow. /// /// Same as [Material.shape](https://docs.flutter.io/flutter/material/Material/shape.html) - final ShapeBorder shape; + final ShapeBorder? shape; /// Defines if a scrollbar will be displayed or not. final bool hasScrollbar; @@ -1287,7 +1277,7 @@ class SuggestionsBoxDecoration { /// If non-null, the corners of this box are rounded by this [BorderRadius](https://docs.flutter.io/flutter/painting/BorderRadius-class.html). /// /// Same as [Material.borderRadius](https://docs.flutter.io/flutter/material/Material/borderRadius.html) - final BorderRadius borderRadius; + final BorderRadius? borderRadius; /// The color to paint the shadow below the material. /// @@ -1295,7 +1285,7 @@ class SuggestionsBoxDecoration { final Color shadowColor; /// The constraints to be applied to the suggestions box - final BoxConstraints constraints; + final BoxConstraints? constraints; /// Adds an offset to the suggestions box final double offsetX; @@ -1315,9 +1305,7 @@ class SuggestionsBoxDecoration { this.shadowColor: const Color(0xFF000000), this.constraints, this.clipBehavior: Clip.none, - this.offsetX: 0.0}) - : assert(shadowColor != null), - assert(elevation != null); + this.offsetX: 0.0}); } /// Supply an instance of this class to the [TypeAhead.textFieldConfiguration] @@ -1347,17 +1335,17 @@ class TextFieldConfiguration { /// } /// ) /// ``` - final TextEditingController controller; + final TextEditingController? controller; /// Controls whether this widget has keyboard focus. /// /// Same as [TextField.focusNode](https://docs.flutter.io/flutter/material/TextField/focusNode.html) - final FocusNode focusNode; + final FocusNode? focusNode; /// The style to use for the text being edited. /// /// Same as [TextField.style](https://docs.flutter.io/flutter/material/TextField/style.html) - final TextStyle style; + final TextStyle? style; /// How the text being edited should be aligned horizontally. /// @@ -1367,7 +1355,7 @@ class TextFieldConfiguration { /// Same as [TextField.textDirection](https://docs.flutter.io/flutter/material/TextField/textDirection.html) /// /// Defaults to null - final TextDirection textDirection; + final TextDirection? textDirection; /// If false the textfield is "disabled": it ignores taps and its /// [decoration] is rendered in grey. @@ -1394,7 +1382,7 @@ class TextFieldConfiguration { /// Optional input validation and formatting overrides. /// /// Same as [TextField.inputFormatters](https://docs.flutter.io/flutter/material/TextField/inputFormatters.html) - final List inputFormatters; + final List? inputFormatters; /// Whether to enable autocorrection. /// @@ -1409,13 +1397,13 @@ class TextFieldConfiguration { /// The minimum number of lines to occupy when the content spans fewer lines. /// /// Same as [TextField.minLines](https://docs.flutter.io/flutter/material/TextField/minLines.html) - final int minLines; + final int? minLines; /// The maximum number of characters (Unicode scalar values) to allow in the /// text field. /// /// Same as [TextField.maxLength](https://docs.flutter.io/flutter/material/TextField/maxLength.html) - final int maxLength; + final int? maxLength; /// If true, prevents the field from allowing more than [maxLength] /// characters. @@ -1431,23 +1419,23 @@ class TextFieldConfiguration { /// Called when the text being edited changes. /// /// Same as [TextField.onChanged](https://docs.flutter.io/flutter/material/TextField/onChanged.html) - final ValueChanged onChanged; + final ValueChanged? onChanged; /// Called when the user indicates that they are done editing the text in the /// field. /// /// Same as [TextField.onSubmitted](https://docs.flutter.io/flutter/material/TextField/onSubmitted.html) - final ValueChanged onSubmitted; + final ValueChanged? onSubmitted; /// The color to use when painting the cursor. /// /// Same as [TextField.cursorColor](https://docs.flutter.io/flutter/material/TextField/cursorColor.html) - final Color cursorColor; + final Color? cursorColor; /// How rounded the corners of the cursor should be. By default, the cursor has a null Radius /// /// Same as [TextField.cursorRadius](https://docs.flutter.io/flutter/material/TextField/cursorRadius.html) - final Radius cursorRadius; + final Radius? cursorRadius; /// How thick the cursor will be. /// @@ -1457,17 +1445,17 @@ class TextFieldConfiguration { /// The appearance of the keyboard. /// /// Same as [TextField.keyboardAppearance](https://docs.flutter.io/flutter/material/TextField/keyboardAppearance.html) - final Brightness keyboardAppearance; + final Brightness? keyboardAppearance; /// Called when the user submits editable content (e.g., user presses the "done" button on the keyboard). /// /// Same as [TextField.onEditingComplete](https://docs.flutter.io/flutter/material/TextField/onEditingComplete.html) - final VoidCallback onEditingComplete; + final VoidCallback? onEditingComplete; /// Called for each distinct tap except for every second tap of a double tap. /// /// Same as [TextField.onTap](https://docs.flutter.io/flutter/material/TextField/onTap.html) - final GestureTapCallback onTap; + final GestureTapCallback? onTap; /// Configures padding to edges surrounding a Scrollable when the Textfield scrolls into view. /// @@ -1482,7 +1470,7 @@ class TextFieldConfiguration { /// The type of action button to use for the keyboard. /// /// Same as [TextField.textInputAction](https://docs.flutter.io/flutter/material/TextField/textInputAction.html) - final TextInputAction textInputAction; + final TextInputAction? textInputAction; final bool enableInteractiveSelection; @@ -1522,35 +1510,35 @@ class TextFieldConfiguration { /// Copies the [TextFieldConfiguration] and only changes the specified /// properties copyWith( - {InputDecoration decoration, - TextStyle style, - TextEditingController controller, - ValueChanged onChanged, - ValueChanged onSubmitted, - bool obscureText, - bool maxLengthEnforced, - int maxLength, - int maxLines, - int minLines, - bool autocorrect, - List inputFormatters, - bool autofocus, - TextInputType keyboardType, - bool enabled, - bool enableSuggestions, - TextAlign textAlign, - FocusNode focusNode, - Color cursorColor, - Radius cursorRadius, - double cursorWidth, - Brightness keyboardAppearance, - VoidCallback onEditingComplete, - GestureTapCallback onTap, - EdgeInsets scrollPadding, - TextCapitalization textCapitalization, - TextDirection textDirection, - TextInputAction textInputAction, - bool enableInteractiveSelection}) { + {InputDecoration? decoration, + TextStyle? style, + TextEditingController? controller, + ValueChanged? onChanged, + ValueChanged? onSubmitted, + bool? obscureText, + bool? maxLengthEnforced, + int? maxLength, + int? maxLines, + int? minLines, + bool? autocorrect, + List? inputFormatters, + bool? autofocus, + TextInputType? keyboardType, + bool? enabled, + bool? enableSuggestions, + TextAlign? textAlign, + FocusNode? focusNode, + Color? cursorColor, + Radius? cursorRadius, + double? cursorWidth, + Brightness? keyboardAppearance, + VoidCallback? onEditingComplete, + GestureTapCallback? onTap, + EdgeInsets? scrollPadding, + TextCapitalization? textCapitalization, + TextDirection? textDirection, + TextInputAction? textInputAction, + bool? enableInteractiveSelection}) { return TextFieldConfiguration( decoration: decoration ?? this.decoration, style: style ?? this.style, @@ -1594,7 +1582,7 @@ class _SuggestionsBox { final AxisDirection desiredDirection; final bool autoFlipDirection; - OverlayEntry _overlayEntry; + OverlayEntry? _overlayEntry; AxisDirection direction; bool isOpened = false; @@ -1602,7 +1590,7 @@ class _SuggestionsBox { double maxHeight = 300.0; double textBoxWidth = 100.0; double textBoxHeight = 100.0; - double directionUpOffset; + late double directionUpOffset; _SuggestionsBox(this.context, this.direction, this.autoFlipDirection) : desiredDirection = direction; @@ -1610,14 +1598,14 @@ class _SuggestionsBox { void open() { if (this.isOpened) return; assert(this._overlayEntry != null); - Overlay.of(context).insert(this._overlayEntry); + Overlay.of(context)!.insert(this._overlayEntry!); this.isOpened = true; } void close() { if (!this.isOpened) return; assert(this._overlayEntry != null); - this._overlayEntry.remove(); + this._overlayEntry!.remove(); this.isOpened = false; } @@ -1629,8 +1617,8 @@ class _SuggestionsBox { } } - MediaQuery _findRootMediaQuery() { - MediaQuery rootMediaQuery; + MediaQuery? _findRootMediaQuery() { + MediaQuery? rootMediaQuery; context.visitAncestorElements((element) { if (element.widget is MediaQuery) { rootMediaQuery = element.widget as MediaQuery; @@ -1647,7 +1635,7 @@ class _SuggestionsBox { // initial viewInsets which are before the keyboard is toggled EdgeInsets initial = MediaQuery.of(context).viewInsets; // initial MediaQuery for orientation change - MediaQuery initialRootMediaQuery = _findRootMediaQuery(); + MediaQuery? initialRootMediaQuery = _findRootMediaQuery(); int timer = 0; // viewInsets or MediaQuery have changed once keyboard has toggled or orientation has changed @@ -1672,7 +1660,7 @@ class _SuggestionsBox { // user may have closed the widget with the keyboard still open if (widgetMounted) { _adjustMaxHeightAndOrientation(); - _overlayEntry.markNeedsBuild(); + _overlayEntry!.markNeedsBuild(); } } @@ -1681,7 +1669,7 @@ class _SuggestionsBox { void _adjustMaxHeightAndOrientation() { TypeAheadField widget = context.widget as TypeAheadField; - RenderBox box = context.findRenderObject(); + RenderBox box = context.findRenderObject() as RenderBox; textBoxWidth = box.size.width; textBoxHeight = box.size.height; @@ -1694,7 +1682,7 @@ class _SuggestionsBox { // we need to find the root MediaQuery for the unsafe area height // we cannot use BuildContext.ancestorWidgetOfExactType because // widgets like SafeArea creates a new MediaQuery with the padding removed - MediaQuery rootMediaQuery = _findRootMediaQuery(); + MediaQuery rootMediaQuery = _findRootMediaQuery()!; // height of keyboard double keyboardHeight = rootMediaQuery.data.viewInsets.bottom; @@ -1746,9 +1734,8 @@ class _SuggestionsBox { double textBoxAbsY) { // unsafe area, ie: iPhone X 'home button' // keyboardHeight includes unsafeAreaHeight, if keyboard is showing, set to 0 - double unsafeAreaHeight = keyboardHeight == 0 && rootMediaQuery != null - ? rootMediaQuery.data.padding.bottom - : 0; + double unsafeAreaHeight = + keyboardHeight == 0 ? rootMediaQuery.data.padding.bottom : 0; return windowHeight - keyboardHeight - @@ -1794,22 +1781,22 @@ class _SuggestionsBox { /// Supply an instance of this class to the [TypeAhead.suggestionsBoxController] /// property to manually control the suggestions box class SuggestionsBoxController { - _SuggestionsBox _suggestionsBox; - FocusNode _effectiveFocusNode; + _SuggestionsBox? _suggestionsBox; + FocusNode? _effectiveFocusNode; /// Opens the suggestions box void open() { - _effectiveFocusNode.requestFocus(); + _effectiveFocusNode!.requestFocus(); } /// Closes the suggestions box void close() { - _effectiveFocusNode.unfocus(); + _effectiveFocusNode!.unfocus(); } /// Opens the suggestions box if closed and vice-versa void toggle() { - if (_suggestionsBox.isOpened) { + if (_suggestionsBox!.isOpened) { close(); } else { open(); @@ -1818,6 +1805,6 @@ class SuggestionsBoxController { /// Recalculates the height of the suggestions box void resize() { - _suggestionsBox.resize(); + _suggestionsBox!.resize(); } } diff --git a/pubspec.yaml b/pubspec.yaml index cc32f2e7..4b9522cc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_typeahead -version: 2.0.0 +version: 2.1.0-nullsafety.0 description: Ahighly customizable typeahead (autocomplete) text input field for Flutter homepage: https://github.com/AbdulRahmanAlHamali/flutter_typeahead @@ -7,13 +7,13 @@ homepage: https://github.com/AbdulRahmanAlHamali/flutter_typeahead dependencies: flutter: sdk: flutter - flutter_keyboard_visibility: ^4.0.2 + flutter_keyboard_visibility: ^5.0.0-nullsafety.0 dev_dependencies: flutter_test: sdk: flutter environment: - sdk: '>=1.19.0 <3.0.0' + sdk: '>=2.12.0-0 <3.0.0' # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec diff --git a/test/flutter_typeahead_test.dart b/test/flutter_typeahead_test.dart index cba52645..854b7c7c 100644 --- a/test/flutter_typeahead_test.dart +++ b/test/flutter_typeahead_test.dart @@ -4,12 +4,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; class TestPage extends StatefulWidget { - - TestPage({Key key}) : super(key: key); + TestPage({Key? key}) : super(key: key); @override State createState() => TestPageState(); } + class TestPageState extends State { final _formKey = GlobalKey(); final TextEditingController _controller = TextEditingController(); @@ -19,16 +19,18 @@ class TestPageState extends State { super.initState(); _controller.text = 'Default text'; } + @override void dispose() { _controller.dispose(); super.dispose(); } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Test'), + title: Text('Test'), ), // https://medium.com/flutterpub/create-beautiful-forms-with-flutter-47075cfe712 body: Form( @@ -37,40 +39,38 @@ class TestPageState extends State { padding: EdgeInsets.symmetric(horizontal: 16.0), children: [ TypeAheadFormField( - textFieldConfiguration: TextFieldConfiguration ( - autofocus: true, - inputFormatters: [LengthLimitingTextInputFormatter(50)], - controller: _controller, - decoration: InputDecoration( - labelText: 'Type Ahead' - ) - ), - suggestionsCallback: (pattern) { - if (pattern != null && pattern.length > 0) return [pattern + 'aaa', pattern + 'bbb']; - else return null; - }, - noItemsFoundBuilder: (context) => null, - itemBuilder: (context, suggestion) { - return ListTile( - title: Text(suggestion), - ); - }, - onSuggestionSelected: (suggestion) => this._controller.text = suggestion - ), + textFieldConfiguration: TextFieldConfiguration( + autofocus: true, + inputFormatters: [LengthLimitingTextInputFormatter(50)], + controller: _controller, + decoration: InputDecoration(labelText: 'Type Ahead')), + suggestionsCallback: (pattern) { + if (pattern.length > 0) + return [pattern + 'aaa', pattern + 'bbb']; + else + return []; + }, + noItemsFoundBuilder: (context) => const SizedBox(), + itemBuilder: (context, dynamic suggestion) { + return ListTile( + title: Text(suggestion), + ); + }, + onSuggestionSelected: (dynamic suggestion) => + this._controller.text = suggestion), ], ), - ) - ); + )); } } void main() { - testWidgets('Test load and dispose TypeAheadFormField', (WidgetTester tester) async { + testWidgets('Test load and dispose TypeAheadFormField', + (WidgetTester tester) async { await tester.pumpWidget(MaterialApp(home: TestPage())); await tester.pumpAndSettle(); expect(find.text('Type Ahead'), findsOneWidget); expect(find.text('Default text'), findsOneWidget); - }); }