From 8b3b22b23cde3a8111522633f8ee623a57aa68bd Mon Sep 17 00:00:00 2001 From: m1roxx Date: Fri, 28 Aug 2026 13:31:27 +0500 Subject: [PATCH] [material_ui] Let TabBarThemeData.indicator satisfy the indicatorWeight assert TabBar asserted `indicator != null || indicatorWeight > 0.0` in its constructors, where the resolved TabBarThemeData is not available. An app-wide indicator supplied via TabBarThemeData.indicator therefore could not be combined with `indicatorWeight: 0`, even though indicatorWeight is documented to be ignored whenever an indicator is provided by the widget or the theme. Moves the check to _getIndicator, which runs after both the widget-level and theme-level indicators have been ruled out, so it now fires only when the TabBar actually draws its default underline indicator. Fixes https://github.com/flutter/flutter/issues/188837 --- packages/material_ui/lib/src/tabs.dart | 16 +++-- ...e_2026_08_28_tab_bar_indicator_weight.yaml | 3 + packages/material_ui/test/tabs_test.dart | 69 +++++++++++++++++++ 3 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 packages/material_ui/pending_changelogs/change_2026_08_28_tab_bar_indicator_weight.yaml diff --git a/packages/material_ui/lib/src/tabs.dart b/packages/material_ui/lib/src/tabs.dart index 8a0d0789c06d..322e6fdd2905 100644 --- a/packages/material_ui/lib/src/tabs.dart +++ b/packages/material_ui/lib/src/tabs.dart @@ -1066,8 +1066,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { this.tabAlignment, this.textScaler, this.indicatorAnimation, - }) : _isPrimary = true, - assert(indicator != null || (indicatorWeight > 0.0)); + }) : _isPrimary = true; /// Creates a Material Design secondary tab bar. /// @@ -1128,8 +1127,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { this.tabAlignment, this.textScaler, this.indicatorAnimation, - }) : _isPrimary = false, - assert(indicator != null || (indicatorWeight > 0.0)); + }) : _isPrimary = false; /// Typically a list of two or more [Tab] widgets. /// @@ -1173,7 +1171,8 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { /// The thickness of the line that appears below the selected tab. /// - /// The value of this parameter must be greater than zero. + /// The value of this parameter must be greater than zero, unless an indicator + /// is provided by [indicator] or [TabBarThemeData.indicator]. /// /// If [ThemeData.useMaterial3] is true and [TabBar] is used to create a /// primary tab bar, the default value is 3.0. If the provided value is less @@ -1645,6 +1644,13 @@ class _TabBarState extends State { return tabBarTheme.indicator!; } + assert( + widget.indicatorWeight > 0.0, + 'The indicatorWeight must be greater than zero when the TabBar draws its ' + 'default underline indicator, i.e. when no indicator is provided by the ' + 'TabBar or by TabBarThemeData.indicator.', + ); + Color color = widget.indicatorColor ?? tabBarTheme.indicatorColor ?? _defaults.indicatorColor!; // ThemeData tries to avoid this by having indicatorColor avoid being the // primaryColor. However, it's possible that the tab bar is on a diff --git a/packages/material_ui/pending_changelogs/change_2026_08_28_tab_bar_indicator_weight.yaml b/packages/material_ui/pending_changelogs/change_2026_08_28_tab_bar_indicator_weight.yaml new file mode 100644 index 000000000000..164a49e4c96e --- /dev/null +++ b/packages/material_ui/pending_changelogs/change_2026_08_28_tab_bar_indicator_weight.yaml @@ -0,0 +1,3 @@ +changelog: | + - Fixes `TabBar` asserting on construction when `indicatorWeight` is zero and the indicator is provided by `TabBarThemeData.indicator`. +version: patch diff --git a/packages/material_ui/test/tabs_test.dart b/packages/material_ui/test/tabs_test.dart index d3412668ffb3..c1f5ee480426 100644 --- a/packages/material_ui/test/tabs_test.dart +++ b/packages/material_ui/test/tabs_test.dart @@ -3160,6 +3160,75 @@ void main() { ); }); + // Regression test for https://github.com/flutter/flutter/issues/188837. + testWidgets('TabBar.indicatorWeight can be zero when the indicator comes from the theme', ( + WidgetTester tester, + ) async { + const indicatorColor = Color(0xFF00FF00); + const tabBarTheme = TabBarThemeData( + indicator: BoxDecoration(color: indicatorColor), + indicatorSize: TabBarIndicatorSize.tab, + ); + + final tabs = List.generate(2, (int index) => Tab(text: 'Tab $index')); + + Widget buildTabBar({bool secondaryTabBar = false}) { + final TabController controller = createTabController( + vsync: const TestVSync(), + length: tabs.length, + ); + return boilerplate( + useMaterial3: false, + tabBarTheme: tabBarTheme, + child: Container( + alignment: Alignment.topLeft, + child: secondaryTabBar + ? TabBar.secondary(indicatorWeight: 0.0, controller: controller, tabs: tabs) + : TabBar(indicatorWeight: 0.0, controller: controller, tabs: tabs), + ), + ); + } + + await tester.pumpWidget(buildTabBar()); + expect(tester.takeException(), isNull); + + RenderBox tabBarBox = tester.firstRenderObject(find.byType(TabBar)); + // 46 = _kTabHeight(46) + indicatorWeight(0.0) + expect(tabBarBox.size.height, 46.0); + expect( + tabBarBox, + paints..rect(rect: const Rect.fromLTRB(0.0, 0.0, 400.0, 46.0), color: indicatorColor), + ); + + await tester.pumpWidget(buildTabBar(secondaryTabBar: true)); + expect(tester.takeException(), isNull); + + tabBarBox = tester.firstRenderObject(find.byType(TabBar)); + expect(tabBarBox.size.height, 46.0); + expect( + tabBarBox, + paints..rect(rect: const Rect.fromLTRB(0.0, 0.0, 400.0, 46.0), color: indicatorColor), + ); + }); + + testWidgets('TabBar asserts when indicatorWeight is zero and no indicator is provided', ( + WidgetTester tester, + ) async { + final tabs = List.generate(2, (int index) => Tab(text: 'Tab $index')); + final TabController controller = createTabController( + vsync: const TestVSync(), + length: tabs.length, + ); + + await tester.pumpWidget( + boilerplate( + child: TabBar(indicatorWeight: 0.0, controller: controller, tabs: tabs), + ), + ); + + expect(tester.takeException(), isAssertionError); + }); + testWidgets('TabBar with indicatorWeight, indicatorPadding (LTR)', (WidgetTester tester) async { const indicatorColor = Color(0xFF00FF00); const indicatorWeight = 8.0;