-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnavigator.dart
2354 lines (2234 loc) · 85.5 KB
/
navigator.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:developer' as developer;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'basic.dart';
import 'binding.dart';
import 'focus_manager.dart';
import 'focus_scope.dart';
import 'framework.dart';
import 'overlay.dart';
import 'route_notification_messages.dart';
import 'routes.dart';
import 'ticker_provider.dart';
// Examples can assume:
// class MyPage extends Placeholder { MyPage({String title}); }
// class MyHomePage extends Placeholder { }
// NavigatorState navigator;
// BuildContext context;
/// Creates a route for the given route settings.
///
/// Used by [Navigator.onGenerateRoute] and [Navigator.onUnknownRoute].
typedef RouteFactory = Route<dynamic> Function(RouteSettings settings);
/// Signature for the [Navigator.popUntil] predicate argument.
typedef RoutePredicate = bool Function(Route<dynamic> route);
/// Signature for a callback that verifies that it's OK to call [Navigator.pop].
///
/// Used by [Form.onWillPop], [ModalRoute.addScopedWillPopCallback],
/// [ModalRoute.removeScopedWillPopCallback], and [WillPopScope].
typedef WillPopCallback = Future<bool> Function();
/// Indicates whether the current route should be popped.
///
/// Used as the return value for [Route.willPop].
///
/// See also:
///
/// * [WillPopScope], a widget that hooks into the route's [Route.willPop]
/// mechanism.
enum RoutePopDisposition {
/// Pop the route.
///
/// If [Route.willPop] returns [pop] then the back button will actually pop
/// the current route.
pop,
/// Do not pop the route.
///
/// If [Route.willPop] returns [doNotPop] then the back button will be ignored.
doNotPop,
/// Delegate this to the next level of navigation.
///
/// If [Route.willPop] returns [bubble] then the back button will be handled
/// by the [SystemNavigator], which will usually close the application.
bubble,
}
/// Name for the method which is used for sending messages from framework to
/// engine after a route is popped.
const String _routePoppedMethod = 'routePopped';
/// Name for the method which is used for sending messages from framework to
/// engine after a route is pushed.
const String _routePushedMethod = 'routePushed';
/// Name for the method which is used for sending messages from framework to
/// engine after a route is replaced.
const String _routeReplacedMethod = 'routeReplaced';
/// An abstraction for an entry managed by a [Navigator].
///
/// This class defines an abstract interface between the navigator and the
/// "routes" that are pushed on and popped off the navigator. Most routes have
/// visual affordances, which they place in the navigators [Overlay] using one
/// or more [OverlayEntry] objects.
///
/// See [Navigator] for more explanation of how to use a Route
/// with navigation, including code examples.
///
/// See [MaterialPageRoute] for a route that replaces the
/// entire screen with a platform-adaptive transition.
abstract class Route<T> {
/// Initialize the [Route].
///
/// If the [settings] are not provided, an empty [RouteSettings] object is
/// used instead.
Route({ RouteSettings settings }) : settings = settings ?? const RouteSettings();
/// The navigator that the route is in, if any.
NavigatorState get navigator => _navigator;
NavigatorState _navigator;
/// The settings for this route.
///
/// See [RouteSettings] for details.
final RouteSettings settings;
/// The overlay entries for this route.
List<OverlayEntry> get overlayEntries => const <OverlayEntry>[];
/// Called when the route is inserted into the navigator.
///
/// Use this to populate [overlayEntries] and add them to the overlay
/// (accessible as [Navigator.overlay]). (The reason the [Route] is
/// responsible for doing this, rather than the [Navigator], is that the
/// [Route] will be responsible for _removing_ the entries and this way it's
/// symmetric.)
///
/// The `insertionPoint` argument will be null if this is the first route
/// inserted. Otherwise, it indicates the overlay entry to place immediately
/// below the first overlay for this route.
@protected
@mustCallSuper
void install(OverlayEntry insertionPoint) { }
/// Called after [install] when the route is pushed onto the navigator.
///
/// The returned value resolves when the push transition is complete.
///
/// The [didChangeNext] and [didChangePrevious] methods are typically called
/// immediately after this method is called.
@protected
@mustCallSuper
TickerFuture didPush() {
return TickerFuture.complete()..then<void>((void _) {
navigator?.focusScopeNode?.requestFocus();
});
}
/// Called after [install] when the route replaced another in the navigator.
///
/// The [didChangeNext] and [didChangePrevious] methods are typically called
/// immediately after this method is called.
@protected
@mustCallSuper
void didReplace(Route<dynamic> oldRoute) { }
/// Returns whether this route wants to veto a [Navigator.pop]. This method is
/// called by [Navigator.maybePop].
///
/// By default, routes veto a pop if they're the first route in the history
/// (i.e., if [isFirst]). This behavior prevents the user from popping the
/// first route off the history and being stranded at a blank screen.
///
/// See also:
///
/// * [Form], which provides a [Form.onWillPop] callback that uses this
/// mechanism.
/// * [WillPopScope], another widget that provides a way to intercept the
/// back button.
Future<RoutePopDisposition> willPop() async {
return isFirst ? RoutePopDisposition.bubble : RoutePopDisposition.pop;
}
/// Whether calling [didPop] would return false.
bool get willHandlePopInternally => false;
/// When this route is popped (see [Navigator.pop]) if the result isn't
/// specified or if it's null, this value will be used instead.
T get currentResult => null;
/// A future that completes when this route is popped off the navigator.
///
/// The future completes with the value given to [Navigator.pop], if any.
Future<T> get popped => _popCompleter.future;
final Completer<T> _popCompleter = Completer<T>();
/// A request was made to pop this route. If the route can handle it
/// internally (e.g. because it has its own stack of internal state) then
/// return false, otherwise return true (by return the value of calling
/// `super.didPop`). Returning false will prevent the default behavior of
/// [NavigatorState.pop].
///
/// When this function returns true, the navigator removes this route from
/// the history but does not yet call [dispose]. Instead, it is the route's
/// responsibility to call [NavigatorState.finalizeRoute], which will in turn
/// call [dispose] on the route. This sequence lets the route perform an
/// exit animation (or some other visual effect) after being popped but prior
/// to being disposed.
@protected
@mustCallSuper
bool didPop(T result) {
didComplete(result);
return true;
}
/// The route was popped or is otherwise being removed somewhat gracefully.
///
/// This is called by [didPop] and in response to [Navigator.pushReplacement].
///
/// The [popped] future is completed by this method.
@protected
@mustCallSuper
void didComplete(T result) {
_popCompleter.complete(result);
}
/// The given route, which was above this one, has been popped off the
/// navigator.
@protected
@mustCallSuper
void didPopNext(Route<dynamic> nextRoute) { }
/// This route's next route has changed to the given new route. This is called
/// on a route whenever the next route changes for any reason, so long as it
/// is in the history, including when a route is first added to a [Navigator]
/// (e.g. by [Navigator.push]), except for cases when [didPopNext] would be
/// called. `nextRoute` will be null if there's no next route.
@protected
@mustCallSuper
void didChangeNext(Route<dynamic> nextRoute) { }
/// This route's previous route has changed to the given new route. This is
/// called on a route whenever the previous route changes for any reason, so
/// long as it is in the history. `previousRoute` will be null if there's no
/// previous route.
@protected
@mustCallSuper
void didChangePrevious(Route<dynamic> previousRoute) { }
/// Called whenever the internal state of the route has changed.
///
/// This should be called whenever [willHandlePopInternally], [didPop],
/// [offstage], or other internal state of the route changes value. It is used
/// by [ModalRoute], for example, to report the new information via its
/// inherited widget to any children of the route.
///
/// See also:
///
/// * [changedExternalState], which is called when the [Navigator] rebuilds.
@protected
@mustCallSuper
void changedInternalState() { }
/// Called whenever the [Navigator] has its widget rebuilt, to indicate that
/// the route may wish to rebuild as well.
///
/// This is called by the [Navigator] whenever the [NavigatorState]'s
/// [widget] changes, for example because the [MaterialApp] has been rebuilt.
/// This ensures that routes that directly refer to the state of the widget
/// that built the [MaterialApp] will be notified when that widget rebuilds,
/// since it would otherwise be difficult to notify the routes that state they
/// depend on may have changed.
///
/// See also:
///
/// * [changedInternalState], the equivalent but for changes to the internal
/// state of the route.
@protected
@mustCallSuper
void changedExternalState() { }
/// The route should remove its overlays and free any other resources.
///
/// This route is no longer referenced by the navigator.
@mustCallSuper
@protected
void dispose() {
_navigator = null;
}
/// Whether this route is the top-most route on the navigator.
///
/// If this is true, then [isActive] is also true.
bool get isCurrent {
return _navigator != null && _navigator._history.last == this;
}
/// Whether this route is the bottom-most route on the navigator.
///
/// If this is true, then [Navigator.canPop] will return false if this route's
/// [willHandlePopInternally] returns false.
///
/// If [isFirst] and [isCurrent] are both true then this is the only route on
/// the navigator (and [isActive] will also be true).
bool get isFirst {
return _navigator != null && _navigator._history.first == this;
}
/// Whether this route is on the navigator.
///
/// If the route is not only active, but also the current route (the top-most
/// route), then [isCurrent] will also be true. If it is the first route (the
/// bottom-most route), then [isFirst] will also be true.
///
/// If a higher route is entirely opaque, then the route will be active but not
/// rendered. It is even possible for the route to be active but for the stateful
/// widgets within the route to not be instantiated. See [ModalRoute.maintainState].
bool get isActive {
return _navigator != null && _navigator._history.contains(this);
}
}
/// Data that might be useful in constructing a [Route].
@immutable
class RouteSettings {
/// Creates data used to construct routes.
const RouteSettings({
this.name,
this.isInitialRoute = false,
this.arguments,
});
/// Creates a copy of this route settings object with the given fields
/// replaced with the new values.
RouteSettings copyWith({
String name,
bool isInitialRoute,
Object arguments,
}) {
return RouteSettings(
name: name ?? this.name,
isInitialRoute: isInitialRoute ?? this.isInitialRoute,
arguments: arguments ?? this.arguments,
);
}
/// The name of the route (e.g., "/settings").
///
/// If null, the route is anonymous.
final String name;
/// Whether this route is the very first route being pushed onto this [Navigator].
///
/// The initial route typically skips any entrance transition to speed startup.
final bool isInitialRoute;
/// The arguments passed to this route.
///
/// May be used when building the route, e.g. in [Navigator.onGenerateRoute].
final Object arguments;
@override
String toString() => '${objectRuntimeType(this, 'RouteSettings')}("$name", $arguments)';
}
/// An interface for observing the behavior of a [Navigator].
class NavigatorObserver {
/// The navigator that the observer is observing, if any.
NavigatorState get navigator => _navigator;
NavigatorState _navigator;
/// The [Navigator] pushed `route`.
///
/// The route immediately below that one, and thus the previously active
/// route, is `previousRoute`.
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) { }
/// The [Navigator] popped `route`.
///
/// The route immediately below that one, and thus the newly active
/// route, is `previousRoute`.
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) { }
/// The [Navigator] removed `route`.
///
/// If only one route is being removed, then the route immediately below
/// that one, if any, is `previousRoute`.
///
/// If multiple routes are being removed, then the route below the
/// bottommost route being removed, if any, is `previousRoute`, and this
/// method will be called once for each removed route, from the topmost route
/// to the bottommost route.
void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) { }
/// The [Navigator] replaced `oldRoute` with `newRoute`.
void didReplace({ Route<dynamic> newRoute, Route<dynamic> oldRoute }) { }
/// The [Navigator]'s route `route` is being moved by a user gesture.
///
/// For example, this is called when an iOS back gesture starts.
///
/// Paired with a call to [didStopUserGesture] when the route is no longer
/// being manipulated via user gesture.
///
/// If present, the route immediately below `route` is `previousRoute`.
/// Though the gesture may not necessarily conclude at `previousRoute` if
/// the gesture is canceled. In that case, [didStopUserGesture] is still
/// called but a follow-up [didPop] is not.
void didStartUserGesture(Route<dynamic> route, Route<dynamic> previousRoute) { }
/// User gesture is no longer controlling the [Navigator].
///
/// Paired with an earlier call to [didStartUserGesture].
void didStopUserGesture() { }
}
/// A widget that manages a set of child widgets with a stack discipline.
///
/// Many apps have a navigator near the top of their widget hierarchy in order
/// to display their logical history using an [Overlay] with the most recently
/// visited pages visually on top of the older pages. Using this pattern lets
/// the navigator visually transition from one page to another by moving the widgets
/// around in the overlay. Similarly, the navigator can be used to show a dialog
/// by positioning the dialog widget above the current page.
///
/// ## Using the Navigator
///
/// Mobile apps typically reveal their contents via full-screen elements
/// called "screens" or "pages". In Flutter these elements are called
/// routes and they're managed by a [Navigator] widget. The navigator
/// manages a stack of [Route] objects and provides methods for managing
/// the stack, like [Navigator.push] and [Navigator.pop].
///
/// When your user interface fits this paradigm of a stack, where the user
/// should be able to _navigate_ back to an earlier element in the stack,
/// the use of routes and the Navigator is appropriate. On certain platforms,
/// such as Android, the system UI will provide a back button (outside the
/// bounds of your application) that will allow the user to navigate back
/// to earlier routes in your application's stack. On platforms that don't
/// have this build-in navigation mechanism, the use of an [AppBar] (typically
/// used in the [Scaffold.appBar] property) can automatically add a back
/// button for user navigation.
///
/// ### Displaying a full-screen route
///
/// Although you can create a navigator directly, it's most common to use
/// the navigator created by a [WidgetsApp] or a [MaterialApp] widget. You
/// can refer to that navigator with [Navigator.of].
///
/// A [MaterialApp] is the simplest way to set things up. The [MaterialApp]'s
/// home becomes the route at the bottom of the [Navigator]'s stack. It is what
/// you see when the app is launched.
///
/// ```dart
/// void main() {
/// runApp(MaterialApp(home: MyAppHome()));
/// }
/// ```
///
/// To push a new route on the stack you can create an instance of
/// [MaterialPageRoute] with a builder function that creates whatever you
/// want to appear on the screen. For example:
///
/// ```dart
/// Navigator.push(context, MaterialPageRoute<void>(
/// builder: (BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(title: Text('My Page')),
/// body: Center(
/// child: FlatButton(
/// child: Text('POP'),
/// onPressed: () {
/// Navigator.pop(context);
/// },
/// ),
/// ),
/// );
/// },
/// ));
/// ```
///
/// The route defines its widget with a builder function instead of a
/// child widget because it will be built and rebuilt in different
/// contexts depending on when it's pushed and popped.
///
/// As you can see, the new route can be popped, revealing the app's home
/// page, with the Navigator's pop method:
///
/// ```dart
/// Navigator.pop(context);
/// ```
///
/// It usually isn't necessary to provide a widget that pops the Navigator
/// in a route with a [Scaffold] because the Scaffold automatically adds a
/// 'back' button to its AppBar. Pressing the back button causes
/// [Navigator.pop] to be called. On Android, pressing the system back
/// button does the same thing.
///
/// ### Using named navigator routes
///
/// Mobile apps often manage a large number of routes and it's often
/// easiest to refer to them by name. Route names, by convention,
/// use a path-like structure (for example, '/a/b/c').
/// The app's home page route is named '/' by default.
///
/// The [MaterialApp] can be created
/// with a [Map<String, WidgetBuilder>] which maps from a route's name to
/// a builder function that will create it. The [MaterialApp] uses this
/// map to create a value for its navigator's [onGenerateRoute] callback.
///
/// ```dart
/// void main() {
/// runApp(MaterialApp(
/// home: MyAppHome(), // becomes the route named '/'
/// routes: <String, WidgetBuilder> {
/// '/a': (BuildContext context) => MyPage(title: 'page A'),
/// '/b': (BuildContext context) => MyPage(title: 'page B'),
/// '/c': (BuildContext context) => MyPage(title: 'page C'),
/// },
/// ));
/// }
/// ```
///
/// To show a route by name:
///
/// ```dart
/// Navigator.pushNamed(context, '/b');
/// ```
///
/// ### Routes can return a value
///
/// When a route is pushed to ask the user for a value, the value can be
/// returned via the [pop] method's result parameter.
///
/// Methods that push a route return a [Future]. The Future resolves when the
/// route is popped and the [Future]'s value is the [pop] method's `result`
/// parameter.
///
/// For example if we wanted to ask the user to press 'OK' to confirm an
/// operation we could `await` the result of [Navigator.push]:
///
/// ```dart
/// bool value = await Navigator.push(context, MaterialPageRoute<bool>(
/// builder: (BuildContext context) {
/// return Center(
/// child: GestureDetector(
/// child: Text('OK'),
/// onTap: () { Navigator.pop(context, true); }
/// ),
/// );
/// }
/// ));
/// ```
///
/// If the user presses 'OK' then value will be true. If the user backs
/// out of the route, for example by pressing the Scaffold's back button,
/// the value will be null.
///
/// When a route is used to return a value, the route's type parameter must
/// match the type of [pop]'s result. That's why we've used
/// `MaterialPageRoute<bool>` instead of `MaterialPageRoute<void>` or just
/// `MaterialPageRoute`. (If you prefer to not specify the types, though, that's
/// fine too.)
///
/// ### Popup routes
///
/// Routes don't have to obscure the entire screen. [PopupRoute]s cover the
/// screen with a [ModalRoute.barrierColor] that can be only partially opaque to
/// allow the current screen to show through. Popup routes are "modal" because
/// they block input to the widgets below.
///
/// There are functions which create and show popup routes. For
/// example: [showDialog], [showMenu], and [showModalBottomSheet]. These
/// functions return their pushed route's Future as described above.
/// Callers can await the returned value to take an action when the
/// route is popped, or to discover the route's value.
///
/// There are also widgets which create popup routes, like [PopupMenuButton] and
/// [DropdownButton]. These widgets create internal subclasses of PopupRoute
/// and use the Navigator's push and pop methods to show and dismiss them.
///
/// ### Custom routes
///
/// You can create your own subclass of one of the widget library route classes
/// like [PopupRoute], [ModalRoute], or [PageRoute], to control the animated
/// transition employed to show the route, the color and behavior of the route's
/// modal barrier, and other aspects of the route.
///
/// The [PageRouteBuilder] class makes it possible to define a custom route
/// in terms of callbacks. Here's an example that rotates and fades its child
/// when the route appears or disappears. This route does not obscure the entire
/// screen because it specifies `opaque: false`, just as a popup route does.
///
/// ```dart
/// Navigator.push(context, PageRouteBuilder(
/// opaque: false,
/// pageBuilder: (BuildContext context, _, __) {
/// return Center(child: Text('My PageRoute'));
/// },
/// transitionsBuilder: (___, Animation<double> animation, ____, Widget child) {
/// return FadeTransition(
/// opacity: animation,
/// child: RotationTransition(
/// turns: Tween<double>(begin: 0.5, end: 1.0).animate(animation),
/// child: child,
/// ),
/// );
/// }
/// ));
/// ```
///
/// The page route is built in two parts, the "page" and the
/// "transitions". The page becomes a descendant of the child passed to
/// the `transitionsBuilder` function. Typically the page is only built once,
/// because it doesn't depend on its animation parameters (elided with `_`
/// and `__` in this example). The transition is built on every frame
/// for its duration.
///
/// ### Nesting Navigators
///
/// An app can use more than one Navigator. Nesting one Navigator below
/// another Navigator can be used to create an "inner journey" such as tabbed
/// navigation, user registration, store checkout, or other independent journeys
/// that represent a subsection of your overall application.
///
/// #### Real World Example
///
/// It is standard practice for iOS apps to use tabbed navigation where each
/// tab maintains its own navigation history. Therefore, each tab has its own
/// [Navigator], creating a kind of "parallel navigation."
///
/// In addition to the parallel navigation of the tabs, it is still possible to
/// launch full-screen pages that completely cover the tabs. For example: an
/// on-boarding flow, or an alert dialog. Therefore, there must exist a "root"
/// [Navigator] that sits above the tab navigation. As a result, each of the
/// tab's [Navigator]s are actually nested [Navigator]s sitting below a single
/// root [Navigator].
///
/// The nested [Navigator]s for tabbed navigation sit in [WidgetApp] and
/// [CupertinoTabView], so you don't need to worry about nested [Navigator]s
/// in this situation, but it's a real world example where nested [Navigator]s
/// are used.
///
/// {@tool sample --template=freeform}
/// The following example demonstrates how a nested [Navigator] can be used to
/// present a standalone user registration journey.
///
/// Even though this example uses two [Navigator]s to demonstrate nested
/// [Navigator]s, a similar result is possible using only a single [Navigator].
///
/// Run this example with `flutter run --route=/signup` to start it with
/// the signup flow instead of on the home page.
///
/// ```dart imports
/// import 'package:flutter/material.dart';
/// ```
///
/// ```dart main
/// void main() => runApp(new MyApp());
/// ```
///
/// ```dart
/// class MyApp extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
/// title: 'Flutter Code Sample for Navigator',
/// // MaterialApp contains our top-level Navigator
/// initialRoute: '/',
/// routes: {
/// '/': (BuildContext context) => HomePage(),
/// '/signup': (BuildContext context) => SignUpPage(),
/// },
/// );
/// }
/// }
///
/// class HomePage extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return DefaultTextStyle(
/// style: Theme.of(context).textTheme.headline4,
/// child: Container(
/// color: Colors.white,
/// alignment: Alignment.center,
/// child: Text('Home Page'),
/// ),
/// );
/// }
/// }
///
/// class CollectPersonalInfoPage extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return DefaultTextStyle(
/// style: Theme.of(context).textTheme.headline4,
/// child: GestureDetector(
/// onTap: () {
/// // This moves from the personal info page to the credentials page,
/// // replacing this page with that one.
/// Navigator.of(context)
/// .pushReplacementNamed('signup/choose_credentials');
/// },
/// child: Container(
/// color: Colors.lightBlue,
/// alignment: Alignment.center,
/// child: Text('Collect Personal Info Page'),
/// ),
/// ),
/// );
/// }
/// }
///
/// class ChooseCredentialsPage extends StatelessWidget {
/// const ChooseCredentialsPage({
/// this.onSignupComplete,
/// });
///
/// final VoidCallback onSignupComplete;
///
/// @override
/// Widget build(BuildContext context) {
/// return GestureDetector(
/// onTap: onSignupComplete,
/// child: DefaultTextStyle(
/// style: Theme.of(context).textTheme.headline4,
/// child: Container(
/// color: Colors.pinkAccent,
/// alignment: Alignment.center,
/// child: Text('Choose Credentials Page'),
/// ),
/// ),
/// );
/// }
/// }
///
/// class SignUpPage extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// // SignUpPage builds its own Navigator which ends up being a nested
/// // Navigator in our app.
/// return Navigator(
/// initialRoute: 'signup/personal_info',
/// onGenerateRoute: (RouteSettings settings) {
/// WidgetBuilder builder;
/// switch (settings.name) {
/// case 'signup/personal_info':
/// // Assume CollectPersonalInfoPage collects personal info and then
/// // navigates to 'signup/choose_credentials'.
/// builder = (BuildContext _) => CollectPersonalInfoPage();
/// break;
/// case 'signup/choose_credentials':
/// // Assume ChooseCredentialsPage collects new credentials and then
/// // invokes 'onSignupComplete()'.
/// builder = (BuildContext _) => ChooseCredentialsPage(
/// onSignupComplete: () {
/// // Referencing Navigator.of(context) from here refers to the
/// // top level Navigator because SignUpPage is above the
/// // nested Navigator that it created. Therefore, this pop()
/// // will pop the entire "sign up" journey and return to the
/// // "/" route, AKA HomePage.
/// Navigator.of(context).pop();
/// },
/// );
/// break;
/// default:
/// throw Exception('Invalid route: ${settings.name}');
/// }
/// return MaterialPageRoute(builder: builder, settings: settings);
/// },
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// [Navigator.of] operates on the nearest ancestor [Navigator] from the given
/// [BuildContext]. Be sure to provide a [BuildContext] below the intended
/// [Navigator], especially in large [build] methods where nested [Navigator]s
/// are created. The [Builder] widget can be used to access a [BuildContext] at
/// a desired location in the widget subtree.
class Navigator extends StatefulWidget {
/// Creates a widget that maintains a stack-based history of child widgets.
///
/// The [onGenerateRoute] argument must not be null.
const Navigator({
Key key,
this.initialRoute,
@required this.onGenerateRoute,
this.onUnknownRoute,
this.observers = const <NavigatorObserver>[],
}) : assert(onGenerateRoute != null),
super(key: key);
/// The name of the first route to show.
///
/// By default, this defers to [dart:ui.Window.defaultRouteName].
///
/// If this string contains any `/` characters, then the string is split on
/// those characters and substrings from the start of the string up to each
/// such character are, in turn, used as routes to push.
///
/// For example, if the route `/stocks/HOOLI` was used as the [initialRoute],
/// then the [Navigator] would push the following routes on startup: `/`,
/// `/stocks`, `/stocks/HOOLI`. This enables deep linking while allowing the
/// application to maintain a predictable route history.
///
/// If any of the intermediate routes doesn't exist, it'll simply be skipped.
/// In the example above, if `/stocks` doesn't have a corresponding route in
/// the app, it'll be skipped and only `/` and `/stocks/HOOLI` will be pushed.
///
/// That said, the full route has to map to something in the app in order for
/// this to work. In our example, `/stocks/HOOLI` has to map to a route in the
/// app. Otherwise, [initialRoute] will be ignored and [defaultRouteName] will
/// be used instead.
final String initialRoute;
/// Called to generate a route for a given [RouteSettings].
final RouteFactory onGenerateRoute;
/// Called when [onGenerateRoute] fails to generate a route.
///
/// This callback is typically used for error handling. For example, this
/// callback might always generate a "not found" page that describes the route
/// that wasn't found.
///
/// Unknown routes can arise either from errors in the app or from external
/// requests to push routes, such as from Android intents.
final RouteFactory onUnknownRoute;
/// A list of observers for this navigator.
final List<NavigatorObserver> observers;
/// The default name for the [initialRoute].
///
/// See also:
///
/// * [dart:ui.Window.defaultRouteName], which reflects the route that the
/// application was started with.
static const String defaultRouteName = '/';
/// Push a named route onto the navigator that most tightly encloses the given
/// context.
///
/// {@template flutter.widgets.navigator.pushNamed}
/// The route name will be passed to that navigator's [onGenerateRoute]
/// callback. The returned route will be pushed into the navigator.
///
/// The new route and the previous route (if any) are notified (see
/// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
/// [Navigator.observers], they will be notified as well (see
/// [NavigatorObserver.didPush]).
///
/// Ongoing gestures within the current route are canceled when a new route is
/// pushed.
///
/// Returns a [Future] that completes to the `result` value passed to [pop]
/// when the pushed route is popped off the navigator.
///
/// The `T` type argument is the type of the return value of the route.
/// {@endtemplate}
///
/// {@template flutter.widgets.navigator.pushNamed.arguments}
/// The provided `arguments` are passed to the pushed route via
/// [RouteSettings.arguments]. Any object can be passed as `arguments` (e.g. a
/// [String], [int], or an instance of a custom `MyRouteArguments` class).
/// Often, a [Map] is used to pass key-value pairs.
///
/// The `arguments` may be used in [Navigator.onGenerateRoute] or
/// [Navigator.onUnknownRoute] to construct the route.
/// {@endtemplate}
///
/// {@tool snippet}
///
/// Typical usage is as follows:
///
/// ```dart
/// void _didPushButton() {
/// Navigator.pushNamed(context, '/settings');
/// }
/// ```
/// {@end-tool}
///
/// {@tool snippet}
///
/// The following example shows how to pass additional `arguments` to the
/// route:
///
/// ```dart
/// void _showBerlinWeather() {
/// Navigator.pushNamed(
/// context,
/// '/weather',
/// arguments: <String, String>{
/// 'city': 'Berlin',
/// 'country': 'Germany',
/// },
/// );
/// }
/// ```
/// {@end-tool}
///
/// {@tool snippet}
///
/// The following example shows how to pass a custom Object to the route:
///
/// ```dart
/// class WeatherRouteArguments {
/// WeatherRouteArguments({ this.city, this.country });
/// final String city;
/// final String country;
///
/// bool get isGermanCapital {
/// return country == 'Germany' && city == 'Berlin';
/// }
/// }
///
/// void _showWeather() {
/// Navigator.pushNamed(
/// context,
/// '/weather',
/// arguments: WeatherRouteArguments(city: 'Berlin', country: 'Germany'),
/// );
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
static Future<T> pushNamed<T extends Object>(
BuildContext context,
String routeName, {
Object arguments,
}) {
return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);
}
/// Replace the current route of the navigator that most tightly encloses the
/// given context by pushing the route named [routeName] and then disposing
/// the previous route once the new route has finished animating in.
///
/// {@template flutter.widgets.navigator.pushReplacementNamed}
/// If non-null, `result` will be used as the result of the route that is
/// removed; the future that had been returned from pushing that old route
/// will complete with `result`. Routes such as dialogs or popup menus
/// typically use this mechanism to return the value selected by the user to
/// the widget that created their route. The type of `result`, if provided,
/// must match the type argument of the class of the old route (`TO`).
///
/// The route name will be passed to the navigator's [onGenerateRoute]
/// callback. The returned route will be pushed into the navigator.
///
/// The new route and the route below the removed route are notified (see
/// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
/// [Navigator.observers], they will be notified as well (see
/// [NavigatorObserver.didReplace]). The removed route is notified once the
/// new route has finished animating (see [Route.didComplete]). The removed
/// route's exit animation is not run (see [popAndPushNamed] for a variant
/// that does animated the removed route).
///
/// Ongoing gestures within the current route are canceled when a new route is
/// pushed.
///
/// Returns a [Future] that completes to the `result` value passed to [pop]
/// when the pushed route is popped off the navigator.
///
/// The `T` type argument is the type of the return value of the new route,
/// and `TO` is the type of the return value of the old route.
/// {@endtemplate}
///
/// {@macro flutter.widgets.navigator.pushNamed.arguments}
///
/// {@tool snippet}
///
/// Typical usage is as follows:
///
/// ```dart
/// void _switchToBrightness() {
/// Navigator.pushReplacementNamed(context, '/settings/brightness');
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
static Future<T> pushReplacementNamed<T extends Object, TO extends Object>(
BuildContext context,
String routeName, {
TO result,
Object arguments,
}) {
return Navigator.of(context).pushReplacementNamed<T, TO>(routeName, arguments: arguments, result: result);
}
/// Pop the current route off the navigator that most tightly encloses the
/// given context and push a named route in its place.
///
/// {@template flutter.widgets.navigator.popAndPushNamed}
/// If non-null, `result` will be used as the result of the route that is
/// popped; the future that had been returned from pushing the popped route
/// will complete with `result`. Routes such as dialogs or popup menus
/// typically use this mechanism to return the value selected by the user to
/// the widget that created their route. The type of `result`, if provided,
/// must match the type argument of the class of the popped route (`TO`).
///
/// The route name will be passed to the navigator's [onGenerateRoute]
/// callback. The returned route will be pushed into the navigator.
///
/// The new route, the old route, and the route below the old route (if any)
/// are all notified (see [Route.didPop], [Route.didComplete],
/// [Route.didPopNext], [Route.didPush], and [Route.didChangeNext]). If the
/// [Navigator] has any [Navigator.observers], they will be notified as well
/// (see [NavigatorObserver.didPop] and [NavigatorObservers.didPush]). The
/// animations for the pop and the push are performed simultaneously, so the
/// route below may be briefly visible even if both the old route and the new
/// route are opaque (see [TransitionRoute.opaque]).
///
/// Ongoing gestures within the current route are canceled when a new route is
/// pushed.
///