[autofill] opt-out instead of opt-in (#86312)
diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart index cbfe083..400d1a4 100644 --- a/packages/flutter/lib/src/cupertino/text_field.dart +++ b/packages/flutter/lib/src/cupertino/text_field.dart
@@ -293,7 +293,7 @@ this.onTap, this.scrollController, this.scrollPhysics, - this.autofillHints, + this.autofillHints = const <String>[], this.restorationId, this.enableIMEPersonalizedLearning = true, }) : assert(textAlign != null), @@ -449,7 +449,7 @@ this.onTap, this.scrollController, this.scrollPhysics, - this.autofillHints, + this.autofillHints = const <String>[], this.restorationId, this.enableIMEPersonalizedLearning = true, }) : assert(textAlign != null), @@ -837,7 +837,7 @@ } } -class _CupertinoTextFieldState extends State<CupertinoTextField> with RestorationMixin, AutomaticKeepAliveClientMixin<CupertinoTextField> implements TextSelectionGestureDetectorBuilderDelegate { +class _CupertinoTextFieldState extends State<CupertinoTextField> with RestorationMixin, AutomaticKeepAliveClientMixin<CupertinoTextField> implements TextSelectionGestureDetectorBuilderDelegate, AutofillClient { final GlobalKey _clearGlobalKey = GlobalKey(); RestorableTextEditingController? _controller; @@ -1098,6 +1098,28 @@ }, ); } + // AutofillClient implementation start. + @override + String get autofillId => _editableText.autofillId; + + @override + void autofill(TextEditingValue newEditingValue) => _editableText.autofill(newEditingValue); + + @override + TextInputConfiguration get textInputConfiguration { + final List<String>? autofillHints = widget.autofillHints?.toList(growable: false); + final AutofillConfiguration autofillConfiguration = autofillHints != null + ? AutofillConfiguration( + uniqueIdentifier: autofillId, + autofillHints: autofillHints, + currentEditingValue: _effectiveController.value, + hintText: widget.placeholder, + ) + : AutofillConfiguration.disabled; + + return _editableText.textInputConfiguration.copyWith(autofillConfiguration: autofillConfiguration); + } + // AutofillClient implementation end. @override Widget build(BuildContext context) { @@ -1242,7 +1264,7 @@ scrollController: widget.scrollController, scrollPhysics: widget.scrollPhysics, enableInteractiveSelection: widget.enableInteractiveSelection, - autofillHints: widget.autofillHints, + autofillClient: this, restorationId: 'editable', enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, ),
diff --git a/packages/flutter/lib/src/material/selectable_text.dart b/packages/flutter/lib/src/material/selectable_text.dart index 17f6a6b..21e0667 100644 --- a/packages/flutter/lib/src/material/selectable_text.dart +++ b/packages/flutter/lib/src/material/selectable_text.dart
@@ -692,6 +692,7 @@ enableInteractiveSelection: widget.enableInteractiveSelection, dragStartBehavior: widget.dragStartBehavior, scrollPhysics: widget.scrollPhysics, + autofillHints: null, ), );
diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index dc712fe..caa6c63 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart
@@ -345,7 +345,7 @@ this.buildCounter, this.scrollController, this.scrollPhysics, - this.autofillHints, + this.autofillHints = const <String>[], this.restorationId, this.enableIMEPersonalizedLearning = true, }) : assert(textAlign != null), @@ -827,7 +827,7 @@ } } -class _TextFieldState extends State<TextField> with RestorationMixin implements TextSelectionGestureDetectorBuilderDelegate { +class _TextFieldState extends State<TextField> with RestorationMixin implements TextSelectionGestureDetectorBuilderDelegate, AutofillClient { RestorableTextEditingController? _controller; TextEditingController get _effectiveController => widget.controller ?? _controller!.value; @@ -1094,6 +1094,29 @@ } } + // AutofillClient implementation start. + @override + String get autofillId => _editableText!.autofillId; + + @override + void autofill(TextEditingValue newEditingValue) => _editableText!.autofill(newEditingValue); + + @override + TextInputConfiguration get textInputConfiguration { + final List<String>? autofillHints = widget.autofillHints?.toList(growable: false); + final AutofillConfiguration autofillConfiguration = autofillHints != null + ? AutofillConfiguration( + uniqueIdentifier: autofillId, + autofillHints: autofillHints, + currentEditingValue: _effectiveController.value, + hintText: (widget.decoration ?? const InputDecoration()).hintText, + ) + : AutofillConfiguration.disabled; + + return _editableText!.textInputConfiguration.copyWith(autofillConfiguration: autofillConfiguration); + } + // AutofillClient implementation end. + @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); @@ -1240,7 +1263,7 @@ dragStartBehavior: widget.dragStartBehavior, scrollController: widget.scrollController, scrollPhysics: widget.scrollPhysics, - autofillHints: widget.autofillHints, + autofillClient: this, autocorrectionTextRectColor: autocorrectionTextRectColor, restorationId: 'editable', enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
diff --git a/packages/flutter/lib/src/services/autofill.dart b/packages/flutter/lib/src/services/autofill.dart index 4a47015..65de3fc 100644 --- a/packages/flutter/lib/src/services/autofill.dart +++ b/packages/flutter/lib/src/services/autofill.dart
@@ -631,12 +631,40 @@ /// Creates autofill related configuration information that can be sent to the /// platform. const AutofillConfiguration({ + required String uniqueIdentifier, + required List<String> autofillHints, + required TextEditingValue currentEditingValue, + String? hintText, + }) : this._( + enabled: true, + uniqueIdentifier: uniqueIdentifier, + autofillHints: autofillHints, + currentEditingValue: currentEditingValue, + hintText: hintText, + ); + + const AutofillConfiguration._({ + required this.enabled, required this.uniqueIdentifier, - required this.autofillHints, + this.autofillHints = const <String>[], + this.hintText, required this.currentEditingValue, }) : assert(uniqueIdentifier != null), assert(autofillHints != null); + /// An [AutofillConfiguration] that indicates the [AutofillClient] does not + /// wish to be autofilled. + static const AutofillConfiguration disabled = AutofillConfiguration._( + enabled: false, + uniqueIdentifier: '', + currentEditingValue: TextEditingValue.empty, + ); + + /// Whether autofill should be enabled for the [AutofillClient]. + /// + /// To retrieve a disabled [AutofillConfiguration], use [disabled]. + final bool enabled; + /// A string that uniquely identifies the current [AutofillClient]. /// /// The identifier needs to be unique within the [AutofillScope] for the @@ -648,7 +676,7 @@ /// A list of strings that helps the autofill service identify the type of the /// [AutofillClient]. /// - /// Must not be null or empty. + /// Must not be null. /// /// {@template flutter.services.AutofillConfiguration.autofillHints} /// For the best results, hint strings need to be understood by the platform's @@ -697,14 +725,23 @@ /// The current [TextEditingValue] of the [AutofillClient]. final TextEditingValue currentEditingValue; + /// The optional hint text placed on the view that typically suggests what + /// sort of input the field accepts, for example "enter your password here". + /// + /// If the developer does not specify any [autofillHints], the [hintText] can + /// be a useful indication to the platform autofill service. + final String? hintText; + /// Returns a representation of this object as a JSON object. - Map<String, dynamic> toJson() { - assert(autofillHints.isNotEmpty); - return <String, dynamic>{ - 'uniqueIdentifier': uniqueIdentifier, - 'hints': autofillHints, - 'editingValue': currentEditingValue.toJSON(), - }; + Map<String, dynamic>? toJson() { + return enabled + ? <String, dynamic>{ + 'uniqueIdentifier': uniqueIdentifier, + 'hints': autofillHints, + 'editingValue': currentEditingValue.toJSON(), + if (hintText != null) 'hintText': hintText, + } + : null; } } @@ -715,7 +752,7 @@ abstract class AutofillClient { /// The unique identifier of this [AutofillClient]. /// - /// Must not be null. + /// Must not be null and the identifier must not be changed. String get autofillId; /// The [TextInputConfiguration] that describes this [AutofillClient]. @@ -726,7 +763,7 @@ /// Requests this [AutofillClient] update its [TextEditingValue] to the given /// value. - void updateEditingValue(TextEditingValue newEditingValue); + void autofill(TextEditingValue newEditingValue); } /// An ordered group within which [AutofillClient]s are logically connected. @@ -806,7 +843,7 @@ TextInputConnection attach(TextInputClient trigger, TextInputConfiguration configuration) { assert(trigger != null); assert( - !autofillClients.any((AutofillClient client) => client.textInputConfiguration.autofillConfiguration == null), + !autofillClients.any((AutofillClient client) => !client.textInputConfiguration.autofillConfiguration.enabled), 'Every client in AutofillScope.autofillClients must enable autofill', );
diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index c19d856..97cf936 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart
@@ -466,7 +466,7 @@ this.inputAction = TextInputAction.done, this.keyboardAppearance = Brightness.light, this.textCapitalization = TextCapitalization.none, - this.autofillConfiguration, + this.autofillConfiguration = AutofillConfiguration.disabled, this.enableIMEPersonalizedLearning = true, }) : assert(inputType != null), assert(obscureText != null), @@ -503,7 +503,7 @@ /// to the platform. This will prevent the corresponding input field from /// participating in autofills triggered by other fields. Additionally, on /// Android and web, setting [autofillConfiguration] to null disables autofill. - final AutofillConfiguration? autofillConfiguration; + final AutofillConfiguration autofillConfiguration; /// {@template flutter.services.TextInputConfiguration.smartDashesType} /// Whether to allow the platform to automatically format dashes. @@ -607,8 +607,41 @@ /// {@endtemplate} final bool enableIMEPersonalizedLearning; + /// Creates a copy of this [TextInputConfiguration] with the given fields + /// replaced with new values. + TextInputConfiguration copyWith({ + TextInputType? inputType, + bool? readOnly, + bool? obscureText, + bool? autocorrect, + SmartDashesType? smartDashesType, + SmartQuotesType? smartQuotesType, + bool? enableSuggestions, + String? actionLabel, + TextInputAction? inputAction, + Brightness? keyboardAppearance, + TextCapitalization? textCapitalization, + bool? enableIMEPersonalizedLearning, + AutofillConfiguration? autofillConfiguration, + }) { + return TextInputConfiguration( + inputType: inputType ?? this.inputType, + readOnly: readOnly ?? this.readOnly, + obscureText: obscureText ?? this.obscureText, + autocorrect: autocorrect ?? this.autocorrect, + smartDashesType: smartDashesType ?? this.smartDashesType, + smartQuotesType: smartQuotesType ?? this.smartQuotesType, + enableSuggestions: enableSuggestions ?? this.enableSuggestions, + inputAction: inputAction ?? this.inputAction, + textCapitalization: textCapitalization ?? this.textCapitalization, + keyboardAppearance: keyboardAppearance ?? this.keyboardAppearance, + enableIMEPersonalizedLearning: enableIMEPersonalizedLearning?? this.enableIMEPersonalizedLearning, + autofillConfiguration: autofillConfiguration ?? this.autofillConfiguration, + ); + } /// Returns a representation of this object as a JSON object. Map<String, dynamic> toJson() { + final Map<String, dynamic>? autofill = autofillConfiguration.toJson(); return <String, dynamic>{ 'inputType': inputType.toJson(), 'readOnly': readOnly, @@ -622,7 +655,7 @@ 'textCapitalization': textCapitalization.toString(), 'keyboardAppearance': keyboardAppearance.toString(), 'enableIMEPersonalizedLearning': enableIMEPersonalizedLearning, - if (autofillConfiguration != null) 'autofill': autofillConfiguration!.toJson(), + if (autofill != null) 'autofill': autofill, }; } } @@ -1470,7 +1503,10 @@ final TextEditingValue textEditingValue = TextEditingValue.fromJSON( editingValue[tag] as Map<String, dynamic>, ); - scope?.getAutofillClient(tag)?.updateEditingValue(textEditingValue); + final AutofillClient? client = scope?.getAutofillClient(tag); + if (client != null && client.textInputConfiguration.autofillConfiguration.enabled) { + client.autofill(textEditingValue); + } } return;
diff --git a/packages/flutter/lib/src/widgets/autofill.dart b/packages/flutter/lib/src/widgets/autofill.dart index 40b8647..d105699 100644 --- a/packages/flutter/lib/src/widgets/autofill.dart +++ b/packages/flutter/lib/src/widgets/autofill.dart
@@ -134,7 +134,7 @@ @override Iterable<AutofillClient> get autofillClients { return _clients.values - .where((AutofillClient client) => client.textInputConfiguration.autofillConfiguration != null); + .where((AutofillClient client) => client.textInputConfiguration.autofillConfiguration.enabled); } /// Adds the [AutofillClient] to this [AutofillGroup]. @@ -155,9 +155,8 @@ /// Removes an [AutofillClient] with the given `autofillId` from this /// [AutofillGroup]. /// - /// Typically, this should be called by autofillable [TextInputClient]s in - /// [State.dispose] and [State.didChangeDependencies], when the input field - /// needs to be removed from the [AutofillGroup] it is currently registered to. + /// Typically, this should be called by a text field when it's being disposed, + /// or before it's registered with a different [AutofillGroup]. /// /// See also: ///
diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 6a657d3..566b233 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart
@@ -456,7 +456,8 @@ paste: true, selectAll: true, ), - this.autofillHints, + this.autofillHints = const <String>[], + this.autofillClient, this.clipBehavior = Clip.hardEdge, this.restorationId, this.scrollBehavior, @@ -499,10 +500,6 @@ assert(dragStartBehavior != null), assert(toolbarOptions != null), assert(clipBehavior != null), - assert( - !readOnly || autofillHints == null, - "Read-only fields can't have autofill hints.", - ), assert(enableIMEPersonalizedLearning != null), _strutStyle = strutStyle, keyboardType = keyboardType ?? _inferKeyboardType(autofillHints: autofillHints, maxLines: maxLines), @@ -1163,15 +1160,17 @@ /// A list of strings that helps the autofill service identify the type of this /// text input. /// - /// When set to null or empty, this text input will not send its autofill - /// information to the platform, preventing it from participating in - /// autofills triggered by a different [AutofillClient], even if they're in the - /// same [AutofillScope]. Additionally, on Android and web, setting this to - /// null or empty will disable autofill for this text field. + /// When set to null, this text input will not send its autofill information + /// to the platform, preventing it from participating in autofills triggered + /// by a different [AutofillClient], even if they're in the same + /// [AutofillScope]. Additionally, on Android and web, setting this to null + /// will disable autofill for this text field. /// /// The minimum platform SDK version that supports Autofill is API level 26 /// for Android, and iOS 10.0 for iOS. /// + /// Defaults to an empty list. + /// /// ### Setting up iOS autofill: /// /// To provide the best user experience and ensure your app fully supports @@ -1229,6 +1228,12 @@ /// {@macro flutter.services.AutofillConfiguration.autofillHints} final Iterable<String>? autofillHints; + /// The [AutofillClient] that controls this input field's autofill behavior. + /// + /// When null, this widget's [EditableTextState] will be used as the + /// [AutofillClient]. This property may override [autofillHints]. + final AutofillClient? autofillClient; + /// {@macro flutter.material.Material.clipBehavior} /// /// Defaults to [Clip.hardEdge]. @@ -1278,12 +1283,11 @@ required Iterable<String>? autofillHints, required int? maxLines, }) { - if (autofillHints?.isEmpty ?? true) { + if (autofillHints == null || autofillHints.isEmpty) { return maxLines == 1 ? TextInputType.text : TextInputType.multiline; } - TextInputType? returnValue; - final String effectiveHint = autofillHints!.first; + final String effectiveHint = autofillHints.first; // On iOS oftentimes specifying a text content type is not enough to qualify // the input field for autofill. The keyboard type also needs to be compatible @@ -1328,7 +1332,10 @@ AutofillHints.username : TextInputType.text, }; - returnValue = iOSKeyboardType[effectiveHint]; + final TextInputType? keyboardType = iOSKeyboardType[effectiveHint]; + if (keyboardType != null) { + return keyboardType; + } break; case TargetPlatform.android: case TargetPlatform.fuchsia: @@ -1338,8 +1345,9 @@ } } - if (returnValue != null || maxLines != 1) - return returnValue ?? TextInputType.multiline; + if (maxLines != 1) { + return TextInputType.multiline; + } const Map<String, TextInputType> inferKeyboardType = <String, TextInputType> { AutofillHints.addressCity : TextInputType.streetAddress, @@ -1474,8 +1482,7 @@ @override AutofillScope? get currentAutofillScope => _currentAutofillScope; - // Is this field in the current autofill context. - bool _isInAutofillContext = false; + AutofillClient get _effectiveAutofillClient => widget.autofillClient ?? this; /// Whether to create an input connection with the platform for text editing /// or not. @@ -1548,8 +1555,7 @@ if (currentAutofillScope != newAutofillGroup) { _currentAutofillScope?.unregister(autofillId); _currentAutofillScope = newAutofillGroup; - newAutofillGroup?.register(this); - _isInAutofillContext = _isInAutofillContext || _shouldBeInAutofillContext; + _currentAutofillScope?.register(_effectiveAutofillClient); } if (!_didAutoFocus && widget.autofocus) { @@ -1574,7 +1580,11 @@ _selectionOverlay?.update(_value); } _selectionOverlay?.handlesVisible = widget.showSelectionHandles; - _isInAutofillContext = _isInAutofillContext || _shouldBeInAutofillContext; + + if (widget.autofillClient != oldWidget.autofillClient) { + _currentAutofillScope?.unregister(oldWidget.autofillClient?.autofillId ?? autofillId); + _currentAutofillScope?.register(_effectiveAutofillClient); + } if (widget.focusNode != oldWidget.focusNode) { oldWidget.focusNode.removeListener(_handleFocusChanged); @@ -1597,7 +1607,7 @@ if (kIsWeb && _hasInputConnection) { if (oldWidget.readOnly != widget.readOnly) { - _textInputConnection!.updateConfig(textInputConfiguration); + _textInputConnection!.updateConfig(_effectiveAutofillClient.textInputConfiguration); } } @@ -1980,8 +1990,9 @@ } bool get _hasInputConnection => _textInputConnection?.attached ?? false; - bool get _needsAutofill => widget.autofillHints?.isNotEmpty ?? false; - bool get _shouldBeInAutofillContext => _needsAutofill && currentAutofillScope != null; + /// Whether to send the autofill information to the autofill service. True by + /// default. + bool get _needsAutofill => widget.autofillHints?.isNotEmpty ?? true; void _openInputConnection() { if (!_shouldCreateInputConnection) { @@ -1999,8 +2010,8 @@ // notified to exclude this field from the autofill context. So we need to // provide the autofillId. _textInputConnection = _needsAutofill && currentAutofillScope != null - ? currentAutofillScope!.attach(this, textInputConfiguration) - : TextInput.attach(this, _createTextInputConfiguration(_isInAutofillContext || _needsAutofill)); + ? currentAutofillScope!.attach(this, _effectiveAutofillClient.textInputConfiguration) + : TextInput.attach(this, _effectiveAutofillClient.textInputConfiguration); _textInputConnection!.show(); _updateSizeAndTransform(); _updateComposingRectIfNeeded(); @@ -2523,8 +2534,17 @@ @override String get autofillId => 'EditableText-$hashCode'; - TextInputConfiguration _createTextInputConfiguration(bool needsAutofillConfiguration) { - assert(needsAutofillConfiguration != null); + @override + TextInputConfiguration get textInputConfiguration { + final List<String>? autofillHints = widget.autofillHints?.toList(growable: false); + final AutofillConfiguration autofillConfiguration = autofillHints != null + ? AutofillConfiguration( + uniqueIdentifier: autofillId, + autofillHints: autofillHints, + currentEditingValue: currentTextEditingValue, + ) + : AutofillConfiguration.disabled; + return TextInputConfiguration( inputType: widget.keyboardType, readOnly: widget.readOnly, @@ -2539,19 +2559,13 @@ ), textCapitalization: widget.textCapitalization, keyboardAppearance: widget.keyboardAppearance, - autofillConfiguration: !needsAutofillConfiguration ? null : AutofillConfiguration( - uniqueIdentifier: autofillId, - autofillHints: widget.autofillHints?.toList(growable: false) ?? <String>[], - currentEditingValue: currentTextEditingValue, - ), + autofillConfiguration: autofillConfiguration, enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, ); } @override - TextInputConfiguration get textInputConfiguration { - return _createTextInputConfiguration(_needsAutofill); - } + void autofill(TextEditingValue value) => updateEditingValue(value); // null if no promptRect should be shown. TextRange? _currentPromptRectRange;
diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index d353797..5af02d5 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart
@@ -4776,6 +4776,22 @@ }, ); + testWidgets('autofill info has placeholder text', (WidgetTester tester) async { + await tester.pumpWidget( + const CupertinoApp( + home: CupertinoTextField( + placeholder: 'placeholder text', + ), + ), + ); + await tester.tap(find.byType(CupertinoTextField)); + + expect( + tester.testTextInput.setClientArgs?['autofill'], + containsPair('hintText', 'placeholder text'), + ); + }); + testWidgets('textDirection is passed to EditableText', (WidgetTester tester) async { await tester.pumpWidget( const CupertinoApp(
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 02bc2d0..e6b0518 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart
@@ -9924,4 +9924,27 @@ expect(prefixTapCount, 1); expect(suffixTapCount, 1); }); + + testWidgets('autofill info has hint text', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Material( + child: Center( + child: TextField( + decoration: InputDecoration( + hintText: 'placeholder text' + ), + ), + ), + ), + ), + ); + + await tester.tap(find.byType(TextField)); + + expect( + tester.testTextInput.setClientArgs?['autofill'], + containsPair('hintText', 'placeholder text'), + ); + }); }
diff --git a/packages/flutter/test/services/autofill_test.dart b/packages/flutter/test/services/autofill_test.dart index 5af15da..0dfdf1d 100644 --- a/packages/flutter/test/services/autofill_test.dart +++ b/packages/flutter/test/services/autofill_test.dart
@@ -10,7 +10,7 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('TextInput message channels', () { + group('AutofillClient', () { late FakeTextChannel fakeTextChannel; final FakeAutofillScope scope = FakeAutofillScope(); @@ -25,21 +25,19 @@ TextInput.setChannel(SystemChannels.textInput); }); - test('throws if the hint list is empty', () async { - Map<String, dynamic>? json; + test('Does not throw if the hint list is empty', () async { + Object? exception; try { - const AutofillConfiguration config = AutofillConfiguration( + const AutofillConfiguration( uniqueIdentifier: 'id', autofillHints: <String>[], currentEditingValue: TextEditingValue.empty, ); - - json = config.toJson(); } catch (e) { - expect(e.toString(), contains('isNotEmpty')); + exception = e; } - expect(json, isNull); + expect(exception, isNull); }); test( @@ -140,6 +138,9 @@ void showAutocorrectionPromptRect(int start, int end) { latestMethodCall = 'showAutocorrectionPromptRect'; } + + @override + void autofill(TextEditingValue newEditingValue) => updateEditingValue(newEditingValue); } class FakeAutofillScope with AutofillScopeMixin implements AutofillScope {
diff --git a/packages/flutter/test/widgets/autofill_group_test.dart b/packages/flutter/test/widgets/autofill_group_test.dart index d611745..6e7dc3f 100644 --- a/packages/flutter/test/widgets/autofill_group_test.dart +++ b/packages/flutter/test/widgets/autofill_group_test.dart
@@ -25,7 +25,7 @@ client1, AutofillGroup( key: innerKey, - child: Column(children: const <Widget>[client2, TextField()]), + child: Column(children: const <Widget>[client2, TextField(autofillHints: null)]), ), ]), ), @@ -36,23 +36,19 @@ final AutofillGroupState innerState = tester.state<AutofillGroupState>(find.byKey(innerKey)); final AutofillGroupState outerState = tester.state<AutofillGroupState>(find.byKey(outerKey)); - final EditableTextState clientState1 = tester.state<EditableTextState>( - find.descendant(of: find.byWidget(client1), matching: find.byType(EditableText)), - ); - final EditableTextState clientState2 = tester.state<EditableTextState>( - find.descendant(of: find.byWidget(client2), matching: find.byType(EditableText)), - ); + final State<TextField> clientState1 = tester.state<State<TextField>>(find.byWidget(client1)); + final State<TextField> clientState2 = tester.state<State<TextField>>(find.byWidget(client2)); - expect(outerState.autofillClients, <EditableTextState>[clientState1]); - // The second TextField doesn't have autofill enabled. - expect(innerState.autofillClients, <EditableTextState>[clientState2]); + expect(outerState.autofillClients.toList(), <State<TextField>>[clientState1]); + // The second TextField in the AutofillGroup doesn't have autofill enabled. + expect(innerState.autofillClients.toList(), <State<TextField>>[clientState2]); }); testWidgets('new clients can be added & removed to a scope', (WidgetTester tester) async { const Key scopeKey = Key('scope'); const TextField client1 = TextField(autofillHints: <String>['1']); - TextField client2 = const TextField(autofillHints: <String>[]); + TextField client2 = const TextField(autofillHints: null); late StateSetter setState; @@ -74,14 +70,10 @@ final AutofillGroupState scopeState = tester.state<AutofillGroupState>(find.byKey(scopeKey)); - final EditableTextState clientState1 = tester.state<EditableTextState>( - find.descendant(of: find.byWidget(client1), matching: find.byType(EditableText)), - ); - final EditableTextState clientState2 = tester.state<EditableTextState>( - find.descendant(of: find.byWidget(client2), matching: find.byType(EditableText)), - ); + final State<TextField> clientState1 = tester.state<State<TextField>>(find.byWidget(client1)); + final State<TextField> clientState2 = tester.state<State<TextField>>(find.byWidget(client2)); - expect(scopeState.autofillClients, <EditableTextState>[clientState1]); + expect(scopeState.autofillClients.toList(), <State<TextField>>[clientState1]); // Add to scope. setState(() { client2 = const TextField(autofillHints: <String>['2']); }); @@ -93,11 +85,11 @@ expect(scopeState.autofillClients.length, 2); // Remove from scope again. - setState(() { client2 = const TextField(autofillHints: <String>[]); }); + setState(() { client2 = const TextField(autofillHints: null); }); await tester.pump(); - expect(scopeState.autofillClients, <EditableTextState>[clientState1]); + expect(scopeState.autofillClients, <State<TextField>>[clientState1]); }); testWidgets('AutofillGroup has the right clients after reparenting', (WidgetTester tester) async { @@ -131,16 +123,9 @@ final AutofillGroupState innerState = tester.state<AutofillGroupState>(find.byKey(innerKey)); final AutofillGroupState outerState = tester.state<AutofillGroupState>(find.byKey(outerKey)); - final EditableTextState clientState1 = tester.state<EditableTextState>( - find.descendant(of: find.byWidget(client1), matching: find.byType(EditableText)), - ); - final EditableTextState clientState2 = tester.state<EditableTextState>( - find.descendant(of: find.byWidget(client2), matching: find.byType(EditableText)), - ); - - final EditableTextState clientState3 = tester.state<EditableTextState>( - find.descendant(of: find.byKey(keyClient3), matching: find.byType(EditableText)), - ); + final State<TextField> clientState1 = tester.state<State<TextField>>(find.byWidget(client1)); + final State<TextField> clientState2 = tester.state<State<TextField>>(find.byWidget(client2)); + final State<TextField> clientState3 = tester.state<State<TextField>>(find.byKey(keyClient3)); await tester.pumpWidget( MaterialApp( @@ -163,7 +148,7 @@ expect(outerState.autofillClients.length, 2); expect(outerState.autofillClients, contains(clientState1)); expect(outerState.autofillClients, contains(clientState3)); - expect(innerState.autofillClients, <EditableTextState>[clientState2]); + expect(innerState.autofillClients, <State<TextField>>[clientState2]); }); testWidgets('disposing AutofillGroups', (WidgetTester tester) async { @@ -270,8 +255,7 @@ // Remove the topmosts group group3. Should commit. setState(() { - children = const <Widget> [ - ]; + children = const <Widget> []; }); await tester.pump();