diff --git a/example/lib/main.dart b/example/lib/main.dart index e85b0fb1..857b9590 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -20,7 +20,7 @@ class MyApp extends StatelessWidget { } class MyHomePage extends StatefulWidget { - MyHomePage({Key key}) : super(key: key); + MyHomePage({Key? key}) : super(key: key); @override _MyHomePageState createState() => _MyHomePageState(); @@ -69,7 +69,7 @@ class _MyHomePageState extends State { children: [ TextField(), TextField(), - ChipsInput( + ChipsInput( key: _chipKey, /*initialValue: [ AppProfile('John Doe', 'jdoe@flutter.io', @@ -199,14 +199,14 @@ class _MyHomePageState extends State { ); }, ),*/ - RaisedButton( - child: Text('Add Chip'), + ElevatedButton( onPressed: () { - _chipKey.currentState.selectSuggestion(AppProfile( + _chipKey.currentState?.selectSuggestion(AppProfile( 'Gina', 'fred@flutter.io', 'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png')); }, + child: Text('Add Chip'), ), ], ), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 3aa75963..7a0e0a8f 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -2,7 +2,7 @@ name: example description: Example Project for flutter_chips_input environment: - sdk: ">=2.10.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" flutter: ">=1.22.0" dependencies: diff --git a/lib/src/chips_input.dart b/lib/src/chips_input.dart index cfc00661..b2460d87 100644 --- a/lib/src/chips_input.dart +++ b/lib/src/chips_input.dart @@ -28,14 +28,14 @@ extension on TextEditingValue { class ChipsInput extends StatefulWidget { const ChipsInput({ - Key key, + Key? key, this.initialValue = const [], this.decoration = const InputDecoration(), this.enabled = true, - @required this.chipBuilder, - @required this.suggestionBuilder, - @required this.findSuggestions, - @required this.onChanged, + required this.chipBuilder, + required this.suggestionBuilder, + required this.findSuggestions, + required this.onChanged, this.onChipTapped, this.maxChips, this.textStyle, @@ -56,28 +56,28 @@ class ChipsInput extends StatefulWidget { super(key: key); final InputDecoration decoration; - final TextStyle textStyle; + final TextStyle? textStyle; final bool enabled; final ChipsInputSuggestions findSuggestions; final ValueChanged> onChanged; @Deprecated('Will be removed in the next major version') - final ValueChanged onChipTapped; + final ValueChanged? onChipTapped; final ChipsBuilder chipBuilder; final ChipsBuilder suggestionBuilder; final List initialValue; - final int maxChips; - final double suggestionsBoxMaxHeight; + final int? maxChips; + final double? suggestionsBoxMaxHeight; final TextInputType inputType; final TextOverflow textOverflow; final bool obscureText; final bool autocorrect; - final String actionLabel; + final String? actionLabel; final TextInputAction inputAction; final Brightness keyboardAppearance; final bool autofocus; final bool allowChipEditing; - final FocusNode focusNode; - final List initialSuggestions; + final FocusNode? focusNode; + final List? initialSuggestions; // final Color cursorColor; @@ -90,13 +90,14 @@ class ChipsInput extends StatefulWidget { class ChipsInputState extends State> implements TextInputClient { Set _chips = {}; - List _suggestions; - final _suggestionsStreamController = StreamController>.broadcast(); + List? _suggestions; + final _suggestionsStreamController = StreamController?>.broadcast(); int _searchId = 0; TextEditingValue _value = TextEditingValue(); + // TextEditingValue _receivedRemoteTextEditingValue; - TextInputConnection _textInputConnection; - SuggestionsBoxController _suggestionsBoxController; + TextInputConnection? _textInputConnection; + SuggestionsBoxController? _suggestionsBoxController; final _layerLink = LayerLink(); final _enteredTexts = {}; @@ -111,33 +112,42 @@ class ChipsInputState extends State> ); bool get _hasInputConnection => - _textInputConnection != null && _textInputConnection.attached; + _textInputConnection != null && _textInputConnection!.attached; bool get _hasReachedMaxChips => - widget.maxChips != null && _chips.length >= widget.maxChips; + widget.maxChips != null && _chips.length >= widget.maxChips!; // FocusAttachment _focusAttachment; - FocusNode _focusNode; - - RenderBox get renderBox => context.findRenderObject(); + FocusNode? _focusNode; @override void initState() { super.initState(); _chips.addAll(widget.initialValue); + final initialText = + String.fromCharCodes(_chips.map((_) => kObjectReplacementChar)); + _value = TextEditingValue( + text: initialText, + selection: TextSelection.collapsed(offset: initialText.length), + ); + _suggestions = widget.initialSuggestions ?.where((r) => !_chips.contains(r)) - ?.toList(growable: false); + .toList(growable: false); // _focusAttachment = _focusNode.attach(context); _suggestionsBoxController = SuggestionsBoxController(context); _focusNode = widget.focusNode ?? FocusNode(); - _focusNode.addListener(_handleFocusChanged); + _focusNode!.addListener(_handleFocusChanged); - WidgetsBinding.instance.addPostFrameCallback((_) async { - _initOverlayEntry(); + WidgetsBinding.instance?.addPostFrameCallback((_) async { + final renderBox = context.findRenderObject() as RenderBox?; + assert(renderBox != null, + "This cannot be null because it's called after the build"); + + _initOverlayEntry(renderBox!); if (mounted && widget.autofocus && _focusNode != null) { - FocusScope.of(context).autofocus(_focusNode); + FocusScope.of(context).autofocus(_focusNode!); } }); } @@ -146,13 +156,13 @@ class ChipsInputState extends State> void dispose() { _closeInputConnectionIfNeeded(); - _focusNode.removeListener(_handleFocusChanged); + _focusNode?.removeListener(_handleFocusChanged); if (null == widget.focusNode) { - _focusNode.dispose(); + _focusNode?.dispose(); } _suggestionsStreamController.close(); - _suggestionsBoxController.close(); + _suggestionsBoxController?.close(); super.dispose(); } @@ -163,12 +173,12 @@ class ChipsInputState extends State> } void _handleFocusChanged() { - if (_focusNode.hasFocus) { + if (_focusNode != null && _focusNode!.hasFocus) { _openInputConnection(); - _suggestionsBoxController.open(); + _suggestionsBoxController?.open(); } else { _closeInputConnectionIfNeeded(); - _suggestionsBoxController.close(); + _suggestionsBoxController?.close(); } if (mounted) { setState(() { @@ -177,9 +187,9 @@ class ChipsInputState extends State> } } - void _initOverlayEntry() { + void _initOverlayEntry(RenderBox renderBox) { // _suggestionsBoxController.close(); - _suggestionsBoxController.overlayEntry = OverlayEntry( + _suggestionsBoxController?.overlayEntry = OverlayEntry( builder: (context) { final size = renderBox.size; final renderBoxOffset = renderBox.localToGlobal(Offset.zero); @@ -190,20 +200,20 @@ class ChipsInputState extends State> renderBoxOffset.dy - size.height; var _suggestionBoxHeight = max(topAvailableSpace, bottomAvailableSpace); - if (null != widget.suggestionsBoxMaxHeight) { + if (widget.suggestionsBoxMaxHeight != null) { _suggestionBoxHeight = - min(_suggestionBoxHeight, widget.suggestionsBoxMaxHeight); + min(_suggestionBoxHeight, widget.suggestionsBoxMaxHeight!); } final showTop = topAvailableSpace > bottomAvailableSpace; // print("showTop: $showTop" ); final compositedTransformFollowerOffset = showTop ? Offset(0, -size.height) : Offset.zero; - return StreamBuilder>( + return StreamBuilder?>( stream: _suggestionsStreamController.stream, initialData: _suggestions, builder: (context, snapshot) { - if (snapshot.hasData && snapshot.data.isNotEmpty) { + if (snapshot.hasData && (snapshot.data ?? []).isNotEmpty) { var suggestionsListView = Material( elevation: 4.0, child: ConstrainedBox( @@ -213,12 +223,13 @@ class ChipsInputState extends State> child: ListView.builder( shrinkWrap: true, padding: EdgeInsets.zero, - itemCount: snapshot.data.length, + itemCount: + snapshot.data == null ? 0 : snapshot.data!.length, itemBuilder: (BuildContext context, int index) { return widget.suggestionBuilder( context, this, - _suggestions[index], + snapshot.data![index], ); }, ), @@ -250,16 +261,16 @@ class ChipsInputState extends State> if (!_hasReachedMaxChips) { _chips.add(data); if (widget.allowChipEditing) { - var enteredText = _value.normalCharactersText ?? ''; + var enteredText = _value.normalCharactersText; if (enteredText.isNotEmpty) _enteredTexts[data] = enteredText; } _updateTextInputState(replaceText: true); _suggestions = null; _suggestionsStreamController.add(_suggestions); - if (widget.maxChips == _chips.length) _suggestionsBoxController.close(); + if (widget.maxChips == _chips.length) _suggestionsBoxController?.close(); } else { - _suggestionsBoxController.close(); + _suggestionsBoxController?.close(); } widget.onChanged(_chips.toList(growable: false)); } @@ -278,12 +289,14 @@ class ChipsInputState extends State> _textInputConnection = TextInput.attach(this, textInputConfiguration) ..setEditingState(_value); } - _textInputConnection.show(); + _textInputConnection?.show(); Future.delayed(const Duration(milliseconds: 100), () { - WidgetsBinding.instance.addPostFrameCallback((_) async { - final RenderBox renderBox = context.findRenderObject(); - await Scrollable.of(context)?.position?.ensureVisible(renderBox); + WidgetsBinding.instance?.addPostFrameCallback((_) async { + final renderBox = context.findRenderObject(); + if (renderBox != null) { + await Scrollable.of(context)?.position.ensureVisible(renderBox); + } }); }); } @@ -300,7 +313,7 @@ class ChipsInputState extends State> void _closeInputConnectionIfNeeded() { if (_hasInputConnection) { - _textInputConnection.close(); + _textInputConnection?.close(); _textInputConnection = null; // _receivedRemoteTextEditingValue = null; } @@ -320,7 +333,7 @@ class ChipsInputState extends State> var removedChip = _chips.last; _chips = Set.of(_chips.take(value.replacementCharactersCount)); widget.onChanged(_chips.toList(growable: false)); - var putText = ''; + String? putText = ''; if (widget.allowChipEditing && _enteredTexts.containsKey(removedChip)) { putText = _enteredTexts[removedChip]; _enteredTexts.remove(removedChip); @@ -345,9 +358,8 @@ class ChipsInputState extends State> //composing: TextRange(start: 0, end: text.length), ); }); - _closeInputConnectionIfNeeded(); //Hack for #34 (https://github.com/danvick/flutter_chips_input/issues/34#issuecomment-684505282). TODO: Find permanent fix _textInputConnection ??= TextInput.attach(this, textInputConfiguration); - _textInputConnection.setEditingState(_value); + _textInputConnection?.setEditingState(_value); // _closeInputConnectionIfNeeded(false); } @@ -358,14 +370,14 @@ class ChipsInputState extends State> case TextInputAction.go: case TextInputAction.send: case TextInputAction.search: - if (_suggestions != null && _suggestions.isNotEmpty) { - selectSuggestion(_suggestions.first); + if (_suggestions != null && _suggestions!.isNotEmpty) { + selectSuggestion(_suggestions!.first); } else { - _focusNode.unfocus(); + _focusNode?.unfocus(); } break; default: - _focusNode.unfocus(); + _focusNode?.unfocus(); break; } } @@ -376,7 +388,7 @@ class ChipsInputState extends State> } @override - void didUpdateWidget(ChipsInput oldWidget) { + void didUpdateWidget(ChipsInput oldWidget) { super.didUpdateWidget(oldWidget); /* if(widget.focusNode != oldWidget.focusNode){ oldWidget.focusNode.removeListener(_handleFocusChanged); @@ -403,7 +415,7 @@ class ChipsInputState extends State> void showAutocorrectionPromptRect(int start, int end) {} @override - AutofillScope get currentAutofillScope => null; + AutofillScope? get currentAutofillScope => null; @override Widget build(BuildContext context) { @@ -427,12 +439,13 @@ class ChipsInputState extends State> maxLines: 1, overflow: widget.textOverflow, style: widget.textStyle ?? - theme.textTheme.subtitle1.copyWith(height: 1.5), + theme.textTheme.subtitle1?.copyWith(height: 1.5), ), ), Flexible( flex: 0, - child: TextCursor(resumed: _focusNode.hasFocus), + child: TextCursor( + resumed: _focusNode != null && _focusNode!.hasFocus), ), ], ), @@ -441,8 +454,8 @@ class ChipsInputState extends State> return NotificationListener( onNotification: (SizeChangedLayoutNotification val) { - WidgetsBinding.instance.addPostFrameCallback((_) async { - _suggestionsBoxController.overlayEntry.markNeedsBuild(); + WidgetsBinding.instance?.addPostFrameCallback((_) async { + _suggestionsBoxController?.overlayEntry?.markNeedsBuild(); }); return true; }, @@ -457,12 +470,12 @@ class ChipsInputState extends State> }, child: InputDecorator( decoration: widget.decoration, - isFocused: _focusNode.hasFocus, + isFocused: _focusNode != null && _focusNode!.hasFocus, isEmpty: _value.text.isEmpty && _chips.isEmpty, child: Wrap( - children: chipsChildren, spacing: 4.0, runSpacing: 4.0, + children: chipsChildren, ), ), ), diff --git a/lib/src/suggestions_box_controller.dart b/lib/src/suggestions_box_controller.dart index 5fbf4baf..df9586e0 100644 --- a/lib/src/suggestions_box_controller.dart +++ b/lib/src/suggestions_box_controller.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class SuggestionsBoxController { final BuildContext context; - OverlayEntry overlayEntry; + OverlayEntry? overlayEntry; bool _isOpened = false; @@ -14,7 +14,7 @@ class SuggestionsBoxController { void open() { if (_isOpened) return; assert(overlayEntry != null); - Overlay.of(context).insert(overlayEntry); + Overlay.of(context)?.insert(overlayEntry!); _isOpened = true; } @@ -22,7 +22,7 @@ class SuggestionsBoxController { // debugPrint("Closing suggestion box"); if (!_isOpened) return; assert(overlayEntry != null); - overlayEntry.remove(); + overlayEntry!.remove(); _isOpened = false; } diff --git a/lib/src/text_cursor.dart b/lib/src/text_cursor.dart index 3a9118e2..cd08fb32 100644 --- a/lib/src/text_cursor.dart +++ b/lib/src/text_cursor.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; class TextCursor extends StatefulWidget { const TextCursor({ - Key key, + Key? key, this.duration = const Duration(milliseconds: 500), this.resumed = false, }) : super(key: key); @@ -19,7 +19,7 @@ class TextCursor extends StatefulWidget { class _TextCursorState extends State with SingleTickerProviderStateMixin { bool _displayed = false; - Timer _timer; + Timer? _timer; @override void initState() { @@ -33,7 +33,7 @@ class _TextCursorState extends State @override void dispose() { - _timer.cancel(); + _timer?.cancel(); super.dispose(); } diff --git a/pubspec.yaml b/pubspec.yaml index fc372cb1..dfe6da32 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ version: 1.10.0 homepage: https://github.com/danvick/flutter_chips_input environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: