Skip to content

Commit 66cd09d

Browse files
authored
Update PopupRoute docs and add an example (#106948)
1 parent a8d0479 commit 66cd09d

File tree

5 files changed

+178
-11
lines changed

5 files changed

+178
-11
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flutter code sample for PopupRoute
6+
7+
import 'package:flutter/material.dart';
8+
9+
void main() => runApp(const PopupRouteApp());
10+
11+
class PopupRouteApp extends StatelessWidget {
12+
const PopupRouteApp({super.key});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
return const MaterialApp(
17+
home: PopupRouteExample(),
18+
);
19+
}
20+
}
21+
22+
class PopupRouteExample extends StatelessWidget {
23+
const PopupRouteExample({super.key});
24+
25+
@override
26+
Widget build(BuildContext context) {
27+
return Scaffold(
28+
body: Center(
29+
child: OutlinedButton(
30+
onPressed: () {
31+
/// This shows a dismissible dialog.
32+
Navigator.of(context).push(DismissibleDialog<void>());
33+
},
34+
child: const Text('Open DismissibleDialog'),
35+
),
36+
),
37+
);
38+
}
39+
}
40+
41+
class DismissibleDialog<T> extends PopupRoute<T> {
42+
@override
43+
Color? get barrierColor => Colors.black.withAlpha(0x50);
44+
45+
/// This allows the popup to be dismissed by tapping the scrim or by
46+
/// pressing escape key on the keyboard.
47+
@override
48+
bool get barrierDismissible => true;
49+
50+
@override
51+
String? get barrierLabel => 'Dismissible Dialog';
52+
53+
@override
54+
Duration get transitionDuration => const Duration(milliseconds: 300);
55+
56+
@override
57+
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
58+
return Center(
59+
/// Provide DefaultTextStyle to ensure that the dialog's text style matches
60+
/// the rest of the text in the app.
61+
child: DefaultTextStyle(
62+
style: Theme.of(context).textTheme.bodyMedium!,
63+
/// `UnconstrainedBox` is used to make the dialog size itself
64+
/// to fit to the size of the content.
65+
child: UnconstrainedBox(
66+
child: Container(
67+
padding: const EdgeInsets.all(20.0),
68+
decoration: BoxDecoration(
69+
borderRadius: BorderRadius.circular(10),
70+
color: Colors.white,
71+
),
72+
child: Column(
73+
children: <Widget>[
74+
Text('Dismissible Dialog', style: Theme.of(context).textTheme.headlineSmall),
75+
const SizedBox(height: 20),
76+
const Text('Tap in the scrim or press escape key to dismiss.'),
77+
],
78+
),
79+
),
80+
),
81+
),
82+
);
83+
}
84+
}

examples/api/lib/widgets/routes/show_general_dialog.0.dart

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,30 @@
66

77
import 'package:flutter/material.dart';
88

9-
void main() => runApp(const MyApp());
9+
void main() => runApp(const GeneralDialogApp());
1010

11-
class MyApp extends StatelessWidget {
12-
const MyApp({super.key});
13-
14-
static const String _title = 'Flutter Code Sample';
11+
class GeneralDialogApp extends StatelessWidget {
12+
const GeneralDialogApp({super.key});
1513

1614
@override
1715
Widget build(BuildContext context) {
1816
return const MaterialApp(
1917
restorationScopeId: 'app',
20-
title: _title,
21-
home: MyStatelessWidget(),
18+
home: GeneralDialogExample(),
2219
);
2320
}
2421
}
2522

26-
class MyStatelessWidget extends StatelessWidget {
27-
const MyStatelessWidget({super.key});
23+
class GeneralDialogExample extends StatelessWidget {
24+
const GeneralDialogExample({super.key});
2825

2926
@override
3027
Widget build(BuildContext context) {
3128
return Scaffold(
3229
body: Center(
3330
child: OutlinedButton(
3431
onPressed: () {
32+
/// This shows an alert dialog.
3533
Navigator.of(context).restorablePush(_dialogBuilder);
3634
},
3735
child: const Text('Open Dialog'),
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter/services.dart';
7+
import 'package:flutter_api_samples/widgets/routes/popup_route.0.dart' as example;
8+
import 'package:flutter_test/flutter_test.dart';
9+
10+
void main() {
11+
testWidgets('Dismiss dialog with tap on the scrim and escape key', (WidgetTester tester) async {
12+
const String dialogText = 'Tap in the scrim or press escape key to dismiss.';
13+
14+
await tester.pumpWidget(
15+
const example.PopupRouteApp(),
16+
);
17+
18+
expect(find.text(dialogText), findsNothing);
19+
20+
// Tap on the button to show the dialog.
21+
await tester.tap(find.byType(OutlinedButton));
22+
await tester.pumpAndSettle();
23+
expect(find.text(dialogText), findsOneWidget);
24+
25+
// Try to dismiss the dialog with a tap on the scrim.
26+
await tester.tapAt(const Offset(10.0, 10.0));
27+
await tester.pumpAndSettle();
28+
expect(find.text(dialogText), findsNothing);
29+
30+
// Open the dialog again.
31+
await tester.tap(find.byType(OutlinedButton));
32+
await tester.pumpAndSettle();
33+
expect(find.text(dialogText), findsOneWidget);
34+
35+
// Try to dismiss the dialog with the escape key.
36+
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
37+
await tester.pumpAndSettle();
38+
expect(find.text(dialogText), findsNothing);
39+
});
40+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_api_samples/widgets/routes/show_general_dialog.0.dart' as example;
7+
import 'package:flutter_test/flutter_test.dart';
8+
9+
void main() {
10+
testWidgets('Open and dismiss general dialog', (WidgetTester tester) async {
11+
const String dialogText = 'Alert!';
12+
13+
await tester.pumpWidget(
14+
const example.GeneralDialogApp(),
15+
);
16+
17+
expect(find.text(dialogText), findsNothing);
18+
19+
// Tap on the button to show the dialog.
20+
await tester.tap(find.byType(OutlinedButton));
21+
await tester.pumpAndSettle();
22+
expect(find.text(dialogText), findsOneWidget);
23+
24+
// Try to dismiss the dialog with a tap on the scrim.
25+
await tester.tapAt(const Offset(10.0, 10.0));
26+
await tester.pumpAndSettle();
27+
expect(find.text(dialogText), findsNothing);
28+
});
29+
}

packages/flutter/lib/src/widgets/routes.dart

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,8 +1210,10 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
12101210
/// For example, when a dialog is on the screen, the page below the dialog is
12111211
/// usually darkened by the modal barrier.
12121212
///
1213-
/// If [barrierDismissible] is true, then tapping this barrier will cause the
1214-
/// current route to be popped (see [Navigator.pop]) with null as the value.
1213+
/// If [barrierDismissible] is true, then tapping this barrier, pressing
1214+
/// the escape key on the keyboard, or calling route popping functions
1215+
/// such as [Navigator.pop] will cause the current route to be popped
1216+
/// with null as the value.
12151217
///
12161218
/// If [barrierDismissible] is false, then tapping the barrier has no effect.
12171219
///
@@ -1226,6 +1228,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
12261228
///
12271229
/// See also:
12281230
///
1231+
/// * [Navigator.pop], which is used to dismiss the route.
12291232
/// * [barrierColor], which controls the color of the scrim for this route.
12301233
/// * [ModalBarrier], the widget that implements this feature.
12311234
/// {@endtemplate}
@@ -1691,6 +1694,19 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
16911694
}
16921695

16931696
/// A modal route that overlays a widget over the current route.
1697+
///
1698+
/// {@macro flutter.widgets.ModalRoute.barrierDismissible}
1699+
///
1700+
/// {@tool dartpad}
1701+
/// This example shows how to create a dialog box that is dismissible.
1702+
///
1703+
/// ** See code in examples/api/lib/widgets/routes/popup_route.0.dart **
1704+
/// {@end-tool}
1705+
///
1706+
/// See also:
1707+
///
1708+
/// * [ModalRoute], which is the base class for this class.
1709+
/// * [Navigator.pop], which is used to dismiss the route.
16941710
abstract class PopupRoute<T> extends ModalRoute<T> {
16951711
/// Initializes the [PopupRoute].
16961712
PopupRoute({

0 commit comments

Comments
 (0)