Description
In flutter/packages#13263, we updated IconButton to resolve its style variant via IconButtonTheme.variantOf(context), which walks up enclosing IconButtonTheme widgets and returns the first non-null variant. This allows an inner IconButtonTheme with variant: null (such as the internal themes created by AppBar, ListTile, or InputDecorator) to override style without resetting an ancestor's StyleVariant (e.g., StyleVariant.material3Expressive).
However, this approach does not work across routes that capture themes using InheritedTheme.capture or InheritedTheme.captureAll (such as showDialog, showModalBottomSheet, and PopupMenuButton).
InheritedTheme.capture only records the closest InheritedTheme instance for each runtimeType. When a widget tree has an outer IconButtonTheme that sets variant and an inner IconButtonTheme with variant: null, capturing themes from inside the inner theme only captures the inner IconButtonTheme. In the new route, IconButtonTheme.variantOf cannot see the outer IconButtonTheme and falls back to ThemeData.iconButtonTheme.variant or StyleVariant.material3.
Reproduction
The following test demonstrates the issue:
testWidgets('InheritedTheme.captureAll preserves the inherited variant', (
WidgetTester tester,
) async {
StyleVariant? capturedVariant;
await tester.pumpWidget(
MaterialApp(
home: IconButtonTheme(
data: const IconButtonThemeData(variant: StyleVariant.material3Expressive),
child: IconButtonTheme(
data: const IconButtonThemeData(
style: ButtonStyle(foregroundColor: WidgetStatePropertyAll<Color>(Color(0xffff0000))),
),
child: Builder(
builder: (BuildContext context) {
return ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext routeContext) {
return InheritedTheme.captureAll(
context,
Builder(
builder: (BuildContext innerContext) {
capturedVariant = IconButtonTheme.variantOf(innerContext);
return const SizedBox();
},
),
);
},
),
);
},
child: const Text('Push'),
);
},
),
),
),
),
);
await tester.tap(find.text('Push'));
await tester.pumpAndSettle();
// Fails: capturedVariant is StyleVariant.material3 instead of StyleVariant.material3Expressive.
expect(capturedVariant, StyleVariant.material3Expressive);
});Context
See the review comment at flutter/packages#13263 (review) for the original discussion. As more widgets adopt StyleVariant (or if other component themes support partial inheritance across enclosing themes), we need to decide how InheritedTheme.capture should handle properties inherited from multiple ancestors of the same theme type.
Activity