Skip to content

Commit 8ff1b6e

Browse files
authored
Fix Scaffold bottomSheet null exceptions (#117008)
* Prevent possibility of null exceptions on widget.bottomSheet * New approach that fixes bug in updating the bottomSheet * Real-world test for bottomSheet error * Allow bottomSheet to animate out after being killed, even if it was rebuilt * Go back to the simple solution of SizedBox.shrink
1 parent ddb7e43 commit 8ff1b6e

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

packages/flutter/lib/src/material/scaffold.dart

+3-1
Original file line numberDiff line numberDiff line change
@@ -2230,7 +2230,9 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
22302230
child: DraggableScrollableActuator(
22312231
child: StatefulBuilder(
22322232
key: _currentBottomSheetKey,
2233-
builder: (BuildContext context, StateSetter setState) => widget.bottomSheet!,
2233+
builder: (BuildContext context, StateSetter setState) {
2234+
return widget.bottomSheet ?? const SizedBox.shrink();
2235+
},
22342236
),
22352237
),
22362238
);

packages/flutter/test/material/scaffold_test.dart

+83
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import 'package:flutter_test/flutter_test.dart';
1111

1212
import '../widgets/semantics_tester.dart';
1313

14+
// From bottom_sheet.dart.
15+
const Duration _bottomSheetExitDuration = Duration(milliseconds: 200);
16+
1417
void main() {
1518
// Regression test for https://github.com/flutter/flutter/issues/103741
1619
testWidgets('extendBodyBehindAppBar change should not cause the body widget lose state', (WidgetTester tester) async {
@@ -2622,6 +2625,86 @@ void main() {
26222625
matchesSemantics(label: 'BottomSheet', hasDismissAction: false),
26232626
);
26242627
});
2628+
2629+
// Regression test for https://github.com/flutter/flutter/issues/117004
2630+
testWidgets('can rebuild and remove bottomSheet at the same time', (WidgetTester tester) async {
2631+
bool themeIsLight = true;
2632+
bool? defaultBottomSheet = true;
2633+
final GlobalKey bottomSheetKey1 = GlobalKey();
2634+
final GlobalKey bottomSheetKey2 = GlobalKey();
2635+
late StateSetter setState;
2636+
2637+
await tester.pumpWidget(
2638+
StatefulBuilder(
2639+
builder: (BuildContext context, StateSetter stateSetter) {
2640+
setState = stateSetter;
2641+
return MaterialApp(
2642+
theme: themeIsLight ? ThemeData.light() : ThemeData.dark(),
2643+
home: Scaffold(
2644+
bottomSheet: defaultBottomSheet == null
2645+
? null
2646+
: defaultBottomSheet!
2647+
? Container(
2648+
key: bottomSheetKey1,
2649+
width: double.infinity,
2650+
height: 100,
2651+
color: Colors.blue,
2652+
child: const Text('BottomSheet'),
2653+
)
2654+
: Container(
2655+
key: bottomSheetKey2,
2656+
width: double.infinity,
2657+
height: 100,
2658+
color: Colors.red,
2659+
child: const Text('BottomSheet'),
2660+
),
2661+
body: const Placeholder(),
2662+
),
2663+
);
2664+
},
2665+
),
2666+
);
2667+
2668+
expect(find.byKey(bottomSheetKey1), findsOneWidget);
2669+
expect(find.byKey(bottomSheetKey2), findsNothing);
2670+
2671+
// Change to the other bottomSheet.
2672+
setState(() {
2673+
defaultBottomSheet = false;
2674+
});
2675+
expect(find.byKey(bottomSheetKey1), findsOneWidget);
2676+
expect(find.byKey(bottomSheetKey2), findsNothing);
2677+
await tester.pumpAndSettle();
2678+
expect(find.byKey(bottomSheetKey1), findsNothing);
2679+
expect(find.byKey(bottomSheetKey2), findsOneWidget);
2680+
2681+
// Set bottomSheet to null, which starts its exit animation.
2682+
setState(() {
2683+
defaultBottomSheet = null;
2684+
});
2685+
expect(find.byKey(bottomSheetKey1), findsNothing);
2686+
expect(find.byKey(bottomSheetKey2), findsOneWidget);
2687+
2688+
// While the bottomSheet is on the way out, change the theme to cause it to
2689+
// rebuild.
2690+
setState(() {
2691+
themeIsLight = false;
2692+
});
2693+
expect(find.byKey(bottomSheetKey1), findsNothing);
2694+
expect(find.byKey(bottomSheetKey2), findsOneWidget);
2695+
2696+
// The most recent bottomSheet remains on screen during the exit animation.
2697+
await tester.pump(_bottomSheetExitDuration);
2698+
expect(find.byKey(bottomSheetKey1), findsNothing);
2699+
expect(find.byKey(bottomSheetKey2), findsOneWidget);
2700+
2701+
// After animating out, the bottomSheet is gone.
2702+
await tester.pumpAndSettle();
2703+
expect(find.byKey(bottomSheetKey1), findsNothing);
2704+
expect(find.byKey(bottomSheetKey2), findsNothing);
2705+
2706+
expect(tester.takeException(), isNull);
2707+
});
26252708
}
26262709

26272710
class _GeometryListener extends StatefulWidget {

0 commit comments

Comments
 (0)