Skip to content

Commit fcf6740

Browse files
authored
Factor BackButton out of AppBar (flutter#8491)
This widget is useful on its own. This patch factors it out of AppBar so folks can use it separately. Fixes flutter#8489
1 parent 17057bb commit fcf6740

File tree

4 files changed

+102
-23
lines changed

4 files changed

+102
-23
lines changed

packages/flutter/lib/material.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export 'src/material/about.dart';
1515
export 'src/material/app.dart';
1616
export 'src/material/app_bar.dart';
1717
export 'src/material/arc.dart';
18+
export 'src/material/back_button.dart';
1819
export 'src/material/bottom_navigation_bar.dart';
1920
export 'src/material/bottom_sheet.dart';
2021
export 'src/material/button.dart';

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

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart';
88
import 'package:flutter/services.dart';
99
import 'package:flutter/widgets.dart';
1010

11+
import 'back_button.dart';
1112
import 'constants.dart';
1213
import 'flexible_space_bar.dart';
1314
import 'icon.dart';
@@ -188,7 +189,7 @@ class AppBar extends StatefulWidget {
188189
/// example, if the [AppBar] is in a [Scaffold] that also has a [Drawer], the
189190
/// [Scaffold] will fill this widget with an [IconButton] that opens the
190191
/// drawer. If there's no [Drawer] and the parent [Navigator] can go back, the
191-
/// [AppBar] will use an [IconButton] that calls [Navigator.pop].
192+
/// [AppBar] will use a [BackButton] that calls [Navigator.maybePop].
192193
final Widget leading;
193194

194195
/// The primary widget displayed in the appbar.
@@ -345,10 +346,6 @@ class _AppBarState extends State<AppBar> {
345346
Scaffold.of(context).openDrawer();
346347
}
347348

348-
void _handleBackButton() {
349-
Navigator.of(context).maybePop();
350-
}
351-
352349
@override
353350
Widget build(BuildContext context) {
354351
final ThemeData themeData = Theme.of(context);
@@ -383,24 +380,8 @@ class _AppBarState extends State<AppBar> {
383380
tooltip: 'Open navigation menu' // TODO(ianh): Figure out how to localize this string
384381
);
385382
} else {
386-
if (_canPop) {
387-
IconData backIcon;
388-
switch (Theme.of(context).platform) {
389-
case TargetPlatform.android:
390-
case TargetPlatform.fuchsia:
391-
backIcon = Icons.arrow_back;
392-
break;
393-
case TargetPlatform.iOS:
394-
backIcon = Icons.arrow_back_ios;
395-
break;
396-
}
397-
assert(backIcon != null);
398-
leading = new IconButton(
399-
icon: new Icon(backIcon),
400-
onPressed: _handleBackButton,
401-
tooltip: 'Back' // TODO(ianh): Figure out how to localize this string
402-
);
403-
}
383+
if (_canPop)
384+
leading = const BackButton();
404385
}
405386
}
406387
if (leading != null) {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2017 The Chromium 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/widgets.dart';
6+
7+
import 'theme.dart';
8+
import 'icon_button.dart';
9+
import 'icon.dart';
10+
import 'icons.dart';
11+
12+
/// A material design back button.
13+
///
14+
/// A [BackButton] is an [IconButton] with a "back" icon appropriate for the
15+
/// current [TargetPlatform]. When pressed, the back button calls
16+
/// [Navigator.maybePop] to return to the previous route.
17+
///
18+
/// When deciding to display a [BackButton], consider using
19+
/// `ModalRoute.of(context)?.canPop` to check whether the current route can be
20+
/// popped. If that value is false (e.g., because the current route is the
21+
/// initial route), the [BackButton] will not have any effect when pressed,
22+
/// which could frustrate the user.
23+
///
24+
/// Requires one of its ancestors to be a [Material] widget.
25+
///
26+
/// See also:
27+
///
28+
/// * [AppBar], which automatically uses a [BackButton] in its
29+
/// [AppBar.leading] slot when appropriate.
30+
/// * [IconButton], which is a more general widget for creating buttons with
31+
/// icons.
32+
class BackButton extends StatelessWidget {
33+
/// Creates an [IconButton] with the appropriate "back" icon for the current
34+
/// target platform.
35+
const BackButton({ Key key }) : super(key: key);
36+
37+
/// Returns tha appropriate "back" icon for the given `platform`.
38+
static IconData getIconData(TargetPlatform platform) {
39+
switch (platform) {
40+
case TargetPlatform.android:
41+
case TargetPlatform.fuchsia:
42+
return Icons.arrow_back;
43+
case TargetPlatform.iOS:
44+
return Icons.arrow_back_ios;
45+
}
46+
assert(false);
47+
return null;
48+
}
49+
50+
@override
51+
Widget build(BuildContext context) {
52+
return new IconButton(
53+
icon: new Icon(getIconData(Theme.of(context).platform)),
54+
tooltip: 'Back', // TODO(ianh): Figure out how to localize this string
55+
onPressed: () {
56+
Navigator.of(context).maybePop();
57+
},
58+
);
59+
}
60+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2016 The Chromium 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_test/flutter_test.dart';
7+
8+
void main() {
9+
testWidgets('BackButton control test', (WidgetTester tester) async {
10+
await tester.pumpWidget(
11+
new MaterialApp(
12+
home: new Material(child: new Text('Home')),
13+
routes: <String, WidgetBuilder>{
14+
'/next': (BuildContext context) {
15+
return new Material(
16+
child: new Center(
17+
child: const BackButton(),
18+
)
19+
);
20+
},
21+
}
22+
)
23+
);
24+
25+
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
26+
27+
await tester.pump();
28+
await tester.pumpUntilNoTransientCallbacks();
29+
30+
await tester.tap(find.byType(BackButton));
31+
32+
await tester.pump();
33+
await tester.pumpUntilNoTransientCallbacks();
34+
35+
expect(find.text('Home'), findsOneWidget);
36+
});
37+
}

0 commit comments

Comments
 (0)