From 75df48ba1ec813d5cc4abaac04c3fb65e4966bd1 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Fri, 10 Oct 2025 11:39:18 +0200 Subject: [PATCH] Cleanup OutlinedButton.icon documentation and implementation (#176630) ## Description This PR cleanup `OutlinedButton.icon` documentation and recent logic change from https://github.com/flutter/flutter/pull/175810. --- .../lib/src/material/outlined_button.dart | 19 ++++------- .../test/material/outlined_button_test.dart | 33 +++++++++++++++++-- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/packages/flutter/lib/src/material/outlined_button.dart b/packages/flutter/lib/src/material/outlined_button.dart index 295954d509a..503fb74a331 100644 --- a/packages/flutter/lib/src/material/outlined_button.dart +++ b/packages/flutter/lib/src/material/outlined_button.dart @@ -86,13 +86,14 @@ class OutlinedButton extends ButtonStyleButton { required super.child, }) : _addPadding = false; - /// Create a text button from a pair of widgets that serve as the button's + /// Create an outlined button from a pair of widgets that serve as the button's /// [icon] and [label]. /// /// The icon and label are arranged in a row and padded by 12 logical pixels /// at the start, and 16 at the end, with an 8 pixel gap in between. /// - /// If [icon] is null, will create an [OutlinedButton] instead. + /// If [icon] is null, this constructor will create an outlined button + /// that doesn't display an icon. /// /// {@macro flutter.material.ButtonStyleButton.iconAlignment} /// @@ -114,10 +115,10 @@ class OutlinedButton extends ButtonStyleButton { super( child: icon != null ? _OutlinedButtonWithIconChild( - iconAlignment: iconAlignment, label: label, - buttonStyle: style, icon: icon, + buttonStyle: style, + iconAlignment: iconAlignment, ) : label, ); @@ -419,13 +420,13 @@ EdgeInsetsGeometry _scaledPadding(BuildContext context) { class _OutlinedButtonWithIconChild extends StatelessWidget { const _OutlinedButtonWithIconChild({ required this.label, - this.icon, + required this.icon, required this.buttonStyle, required this.iconAlignment, }); final Widget label; - final Widget? icon; + final Widget icon; final ButtonStyle? buttonStyle; final IconAlignment? iconAlignment; @@ -442,12 +443,6 @@ class _OutlinedButtonWithIconChild extends StatelessWidget { outlinedButtonTheme.style?.iconAlignment ?? buttonStyle?.iconAlignment ?? IconAlignment.start; - final Widget? icon = this.icon; - - if (icon == null) { - return label; - } - return Row( mainAxisSize: MainAxisSize.min, spacing: lerpDouble(8, 4, scale)!, diff --git a/packages/flutter/test/material/outlined_button_test.dart b/packages/flutter/test/material/outlined_button_test.dart index 792b7f20d4f..b4fc219a225 100644 --- a/packages/flutter/test/material/outlined_button_test.dart +++ b/packages/flutter/test/material/outlined_button_test.dart @@ -1295,7 +1295,7 @@ void main() { // Initially, no icons are present. expect(find.byIcon(Icons.favorite), findsNothing); - // Find the original OutlinedButton with no icon and get it's SemanticsNode + // Find the original OutlinedButton with no icon and get its SemanticsNode. final Finder outlinedButton = find.bySemanticsLabel('Button'); expect(outlinedButton, findsOneWidget); @@ -1308,7 +1308,7 @@ void main() { // Now one icon should be present. expect(find.byIcon(Icons.favorite), findsOneWidget); - // Check if the semantics has change + // Check if the semantics has change. final SemanticsNode semanticsNodeWithIcon = tester.getSemantics(outlinedButton); expect(semanticsNodeWithIcon, origSemanticsNode); @@ -3047,4 +3047,33 @@ void main() { matchesGoldenFile('outlined_button.badge.outline.png'), ); }); + + testWidgets('OutlinedButton.icon does not lose focus when icon is nullified', ( + WidgetTester tester, + ) async { + Widget buildButton({required Widget? icon}) { + return MaterialApp( + home: Center( + child: OutlinedButton.icon(onPressed: () {}, icon: icon, label: const Text('button')), + ), + ); + } + + // Build once with an icon. + await tester.pumpWidget(buildButton(icon: const Icon(Icons.abc))); + + FocusNode getButtonFocusNode() { + return Focus.of(tester.element(find.text('button'))); + } + + getButtonFocusNode().requestFocus(); + await tester.pumpAndSettle(); + expect(getButtonFocusNode().hasFocus, true); + + // Rebuild without icon. + await tester.pumpWidget(buildButton(icon: null)); + + // The button should still be focused. + expect(getButtonFocusNode().hasFocus, true); + }); }