Unverified Commit 30234a00 authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Fix `ExpansionTile` properties cannot be updated with `setState` (#134218)

fixes [`ExpansionTile` properties aren't updated with `setState`](https://github.com/flutter/flutter/issues/24493)

### Code sample

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Example(),
    );
  }
}

class Example extends StatefulWidget {
  const Example({super.key});

  @override
  State<Example> createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  ShapeBorder collapsedShape = const RoundedRectangleBorder(
    borderRadius: BorderRadius.all(Radius.circular(4)),
  );
  Color collapsedTextColor = const Color(0xffffffff);
  Color collapsedBackgroundColor = const Color(0xffff0000);
  Color collapsedIconColor = const Color(0xffffffff);
  ShapeBorder shape = const RoundedRectangleBorder(
    borderRadius: BorderRadius.all(Radius.circular(16)),
  );
  Color backgroundColor = const Color(0xffff0000);
  Color textColor = const Color(0xffffffff);
  Color iconColor = const Color(0xffffffff);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ExpansionTile(
                shape: shape,
                backgroundColor: backgroundColor,
                textColor: textColor,
                iconColor: iconColor,
                collapsedShape: collapsedShape,
                collapsedTextColor: collapsedTextColor,
                collapsedBackgroundColor: collapsedBackgroundColor,
                collapsedIconColor: collapsedIconColor,
                title: const Text('Collapsed ExpansionTile'),
                children: const [
                  ListTile(
                    title: Text('Revealed!'),
                  ),
                ],
              ),
              const SizedBox(height: 16),
              ExpansionTile(
                shape: shape,
                backgroundColor: backgroundColor,
                textColor: textColor,
                iconColor: iconColor,
                initiallyExpanded: true,
                title: const Text('Expanded ExpansionTile'),
                children: const [
                  ListTile(
                    title: Text('Revealed!'),
                  ),
                ],
              ),
              const SizedBox(height: 16),
              FilledButton(
                onPressed: () {
                  setState(() {
                    collapsedShape = const RoundedRectangleBorder(
                      borderRadius: BorderRadius.all(Radius.circular(50)),
                    );
                    collapsedTextColor = const Color(0xfff00000);
                    collapsedBackgroundColor = const Color(0xffffff00);
                    collapsedIconColor = const Color(0xfff00000);

                    shape = const RoundedRectangleBorder();
                    backgroundColor = const Color(0xfffff000);
                    textColor = const Color(0xfff00000);
                    iconColor = const Color(0xfff00000);
                  });
                },
                child: const Text('Update properties'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
```

</details> 

### Before

https://github.com/flutter/flutter/assets/48603081/b29aed98-38ff-40a3-9ed3-c4342ada35b6

### After

https://github.com/flutter/flutter/assets/48603081/5e0b6a34-c577-40ed-8456-7ef55caa277b
parent 2032fcc5
......@@ -668,6 +668,32 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
);
}
@override
void didUpdateWidget(covariant ExpansionTile oldWidget) {
super.didUpdateWidget(oldWidget);
final ThemeData theme = Theme.of(context);
final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of(context);
final ExpansionTileThemeData defaults = theme.useMaterial3
? _ExpansionTileDefaultsM3(context)
: _ExpansionTileDefaultsM2(context);
if (widget.collapsedShape != oldWidget.collapsedShape
|| widget.shape != oldWidget.shape) {
_updateShapeBorder(expansionTileTheme, theme);
}
if (widget.collapsedTextColor != oldWidget.collapsedTextColor
|| widget.textColor != oldWidget.textColor) {
_updateHeaderColor(expansionTileTheme, defaults);
}
if (widget.collapsedIconColor != oldWidget.collapsedIconColor
|| widget.iconColor != oldWidget.iconColor) {
_updateIconColor(expansionTileTheme, defaults);
}
if (widget.backgroundColor != oldWidget.backgroundColor
|| widget.collapsedBackgroundColor != oldWidget.collapsedBackgroundColor) {
_updateBackgroundColor(expansionTileTheme);
}
}
@override
void didChangeDependencies() {
final ThemeData theme = Theme.of(context);
......@@ -675,6 +701,14 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
final ExpansionTileThemeData defaults = theme.useMaterial3
? _ExpansionTileDefaultsM3(context)
: _ExpansionTileDefaultsM2(context);
_updateShapeBorder(expansionTileTheme, theme);
_updateHeaderColor(expansionTileTheme, defaults);
_updateIconColor(expansionTileTheme, defaults);
_updateBackgroundColor(expansionTileTheme);
super.didChangeDependencies();
}
void _updateShapeBorder(ExpansionTileThemeData expansionTileTheme, ThemeData theme) {
_borderTween
..begin = widget.collapsedShape
?? expansionTileTheme.collapsedShape
......@@ -688,20 +722,28 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
top: BorderSide(color: theme.dividerColor),
bottom: BorderSide(color: theme.dividerColor),
);
}
void _updateHeaderColor(ExpansionTileThemeData expansionTileTheme, ExpansionTileThemeData defaults) {
_headerColorTween
..begin = widget.collapsedTextColor
?? expansionTileTheme.collapsedTextColor
?? defaults.collapsedTextColor
..end = widget.textColor ?? expansionTileTheme.textColor ?? defaults.textColor;
}
void _updateIconColor(ExpansionTileThemeData expansionTileTheme, ExpansionTileThemeData defaults) {
_iconColorTween
..begin = widget.collapsedIconColor
?? expansionTileTheme.collapsedIconColor
?? defaults.collapsedIconColor
..end = widget.iconColor ?? expansionTileTheme.iconColor ?? defaults.iconColor;
}
void _updateBackgroundColor(ExpansionTileThemeData expansionTileTheme) {
_backgroundColorTween
..begin = widget.collapsedBackgroundColor ?? expansionTileTheme.collapsedBackgroundColor
..end = widget.backgroundColor ?? expansionTileTheme.backgroundColor;
super.didChangeDependencies();
}
@override
......
......@@ -893,6 +893,162 @@ void main() {
handle.dispose();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgetsWithLeakTracking('Collapsed ExpansionTile properties can be updated with setState', (WidgetTester tester) async {
const Key expansionTileKey = Key('expansionTileKey');
ShapeBorder collapsedShape = const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
);
Color collapsedTextColor = const Color(0xffffffff);
Color collapsedBackgroundColor = const Color(0xffff0000);
Color collapsedIconColor = const Color(0xffffffff);
await tester.pumpWidget(MaterialApp(
home: Material(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(
children: <Widget>[
ExpansionTile(
key: expansionTileKey,
collapsedShape: collapsedShape,
collapsedTextColor: collapsedTextColor,
collapsedBackgroundColor: collapsedBackgroundColor,
collapsedIconColor: collapsedIconColor,
title: const TestText('title'),
trailing: const TestIcon(),
children: const <Widget>[
SizedBox(height: 100, width: 100),
],
),
// This button is used to update the ExpansionTile properties.
FilledButton(
onPressed: () {
setState(() {
collapsedShape = const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
);
collapsedTextColor = const Color(0xff000000);
collapsedBackgroundColor = const Color(0xffffff00);
collapsedIconColor = const Color(0xff000000);
});
},
child: const Text('Update collapsed properties'),
),
],
);
}
),
),
));
ShapeDecoration shapeDecoration = tester.firstWidget<Container>(find.descendant(
of: find.byKey(expansionTileKey),
matching: find.byType(Container),
)).decoration! as ShapeDecoration;
// Test initial ExpansionTile properties.
expect(shapeDecoration.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))));
expect(shapeDecoration.color, const Color(0xffff0000));
expect(tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color, const Color(0xffffffff));
expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xffffffff));
// Tap the button to update the ExpansionTile properties.
await tester.tap(find.text('Update collapsed properties'));
await tester.pumpAndSettle();
shapeDecoration = tester.firstWidget<Container>(find.descendant(
of: find.byKey(expansionTileKey),
matching: find.byType(Container),
)).decoration! as ShapeDecoration;
// Test updated ExpansionTile properties.
expect(shapeDecoration.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))));
expect(shapeDecoration.color, const Color(0xffffff00));
expect(tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color, const Color(0xff000000));
expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xff000000));
});
testWidgetsWithLeakTracking('Expanded ExpansionTile properties can be updated with setState', (WidgetTester tester) async {
const Key expansionTileKey = Key('expansionTileKey');
ShapeBorder shape = const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
);
Color textColor = const Color(0xff00ffff);
Color backgroundColor = const Color(0xff0000ff);
Color iconColor = const Color(0xff00ffff);
await tester.pumpWidget(MaterialApp(
home: Material(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(
children: <Widget>[
ExpansionTile(
key: expansionTileKey,
shape: shape,
textColor: textColor,
backgroundColor: backgroundColor,
iconColor: iconColor,
title: const TestText('title'),
trailing: const TestIcon(),
children: const <Widget>[
SizedBox(height: 100, width: 100),
],
),
// This button is used to update the ExpansionTile properties.
FilledButton(
onPressed: () {
setState(() {
shape = const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6)),
);
textColor = const Color(0xffffffff);
backgroundColor = const Color(0xff123456);
iconColor = const Color(0xffffffff);
});
},
child: const Text('Update collapsed properties'),
),
],
);
}
),
),
));
// Tap to expand the ExpansionTile.
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
ShapeDecoration shapeDecoration = tester.firstWidget<Container>(find.descendant(
of: find.byKey(expansionTileKey),
matching: find.byType(Container),
)).decoration! as ShapeDecoration;
// Test initial ExpansionTile properties.
expect(shapeDecoration.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))));
expect(shapeDecoration.color, const Color(0xff0000ff));
expect(tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color, const Color(0xff00ffff));
expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xff00ffff));
// Tap the button to update the ExpansionTile properties.
await tester.tap(find.text('Update collapsed properties'));
await tester.pumpAndSettle();
shapeDecoration = tester.firstWidget<Container>(find.descendant(
of: find.byKey(expansionTileKey),
matching: find.byType(Container),
)).decoration! as ShapeDecoration;
iconColor = tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color!;
textColor = tester.state<TestTextState>(find.byType(TestText)).textStyle.color!;
// Test updated ExpansionTile properties.
expect(shapeDecoration.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(6))));
expect(shapeDecoration.color, const Color(0xff123456));
expect(tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color, const Color(0xffffffff));
expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xffffffff));
});
group('Material 2', () {
// These tests are only relevant for Material 2. Once Material 2
// support is deprecated and the APIs are removed, these tests
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment