What is the route of a FLUTTER

Routing is used for page forwarding, facilitating page forwarding management and data exchange. Routing makes it easy to move from one page to another, and the underlying system is actually helping us push widgets on and off the stack.

In Android, we start a new page called Activity. In iOS, we open up a new page called ViewControllers. In Flutter, every page is a widget. In Flutter, we use the Api of Navigator to realize page hopping and interaction.

The Navigator Api has been updated to Navigator 2.0 in Flutter 1.22, introducing a new set of declarative apis for routing interactions.

Ii. Principle of flutter routing

The most important thing to develop the interface of FLUTTER is the router. As long as the interface hopping is involved, the routing function is needed. The UI of Flutter is made up of widgets. Looking through the source code, you can see that it is introduced step by step from the root of the layout, the MaterialApp.

The general app root part is the MaterialApp, and its build method builds the WidgetsApp part.

Widget build(BuildContext context) { Widget result = _buildWidgetApp(context); . return ScrollConfiguration( behavior: _MaterialScrollBehavior(), child: HeroControllerScope( controller: _heroController, child: result, ) ); } Widget _buildWidgetApp(BuildContext context) { ... return WidgetsApp( key: GlobalObjectKey(this), navigatorKey: widget.navigatorKey, navigatorObservers: widget.navigatorObservers! , pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) { return MaterialPageRoute<T>(settings: settings, builder: builder); },...). ; }Copy the code

The build method of the WidgetsApp widget will build the Navigator routing widget, and the content widget to be displayed will become the Navigator’s child widget. The operations we want to jump to will generally use navigator.push and other methods.

Widget build(BuildContext context) { Widget? routing; if (_usesRouter) { ... } else if (_usesNavigator) { assert(_navigator ! = null); routing = Navigator( restorationScopeId: 'nav', key: _navigator, initialRoute: _initialRouteName, ... ) ; }}Copy the code

The Navigator widget’s build method eventually displays the routing interface using Overlay.

Widget build(BuildContext context) {
    ...
    return HeroControllerScope.none(
      child: Listener(
        			...
              child: Overlay(
                key: _overlayKey,
                initialEntries: _initialOverlayEntries,
        ),
      ),
    );
  }
Copy the code

The Overlay component stores how many interfaces through the router and adds the interfaces in the form of an _OverlayEntry component to the onstageChildren set, which is a set of components that do not need to be drawn.

Widget build(BuildContext context) {
    final List<Widget> children = <Widget>[];
    bool onstage = true;
    int onstageCount = 0;
    for (int i = _entries.length - 1; i >= 0; i -= 1) {
      final OverlayEntry entry = _entries[i];
      if (onstage) {
        onstageCount += 1;
        children.add(_OverlayEntryWidget(
          key: entry._key,
          entry: entry,
        ));
        if (entry.opaque)
          onstage = false;
      } else if (entry.maintainState) {
        children.add(_OverlayEntryWidget(
          key: entry._key,
          entry: entry,
          tickerEnabled: false,
        ));
      }
    }
    /// Special version of a [Stack], that doesn't layout and render the 						first [skipCount] children.
		/// The first [skipCount] children are considered "offstage".
    return _Theatre(
      skipCount: children.length - onstageCount,
      children: children.reversed.toList(growable: false),
      clipBehavior: widget.clipBehavior,
    );
  }
Copy the code

In other words, the home property of your MaterialApp displays the current routing interface. If you need to jump to route B interface, the INTERFACE B will be displayed on top of the home interface. In other words, if the home interface is used as route A, the interface B will be displayed on top of interface A, so interface A will be overwritten by interface B. This is because the parent component of route A and route B interfaces is Stack.

The default routing interface to get the first, it is in the Navigator defaultGenerateInitialRoutes method.

const Navigator({ ... this.initialRoute, this.onGenerateInitialRoutes = Navigator.defaultGenerateInitialRoutes, ... } //initialRouteName = widget.initialRoute ?? Navigator.defaultRouteName; static List<Route<dynamic>> defaultGenerateInitialRoutes(NavigatorState navigator, String initialRouteName) { final List<Route<dynamic>? > result = <Route<dynamic>? > []; if (initialRouteName.startsWith('/') && initialRouteName.length > 1) { initialRouteName = initialRouteName.substring(1);  // strip leading '/' ... }... return result.cast<Route<dynamic>>(); } / / defaultGenerateInitialRoutes return values assigned to onGenerateInitialRoutes void restoreState (RestorationBucket? oldBucket, bool initialRestore) { ... if (initialRoute ! = null) { _history.addAll( widget.onGenerateInitialRoutes( this, widget.initialRoute ?? Navigator.defaultRouteName, ) ); }}}Copy the code

Widget. initialRoute, which is our custom startup page, lets the manager find the first routing interface that needs to be rendered, DefaultRouteName defaults to “/”, and the Navigator’s push method adds different routing interfaces to the internal _history list, and eventually puts all routing interfaces into _initialOverlayEntries for the Overlay to display.

In summary, the principle of routing described above is that Overlay component is implemented through Stack.

Navigator1.0 instructions

There are two ways of routing in Flutter: one is to configure a defined route list in the routes parameter of the MaterialApp. That is, the page and name to jump to are defined in advance and the jump is performed using this name. OnGenerateRoute is used to set route redirect rules to implement page redirect interactions, and navigatorObservers are available to implement route monitoring.

There are two ways to realize Route in Flutter: one is to use MaterialPageRoute; The other is to use PageRouteBuilder to build.

MaterialPageRoute({// build page @required this.builder, // RouteSettings RouteSettings Settings, MaintainState = true, bool fullscreenDialog = false,})Copy the code
PageRouteBuilder({// Routing Settings RouteSettings Settings, // target page @required this.pageBuilder, / / jump over animation set this. TransitionsBuilder = _defaultTransitionsBuilder, this.transitionDuration = const Duration(milliseconds: 300), ... })Copy the code

Use PageRouteBuilder to build not only to achieve the basic configuration of the route but also to set the jump animation effect.

The page jump transfers parameters. Instead of using the statically defined attribute value in the Routes property of the MaterialApp directly, it uses the dynamic routing method configured with page parameters and obtains parameters through then or await.

// Close the page to return data navigator.pop (context, 'return data '); // Receive the returned data Navigator. Push <String>(Context, MaterialPageRoute(Builder: (BuildContext Context) {return ButtonSamples(title: 'title ', name:' name '); })). Then ((String result) {// handle code});Copy the code

A brief introduction to Navigator2.0

The upgrade of 2.0 routing is more to meet the needs of complex routing on the Web side, but also to meet the concept of state-driven interface design. That is, the interface is separated from the behavior, and the interface is driven to complete the given behavior by changing the state. Therefore, the most critical aspect of 2.0 routing is to change traditional imperative apis such as POP and Push. The interface starts to change data state in response to user actions, and the page routing jump is handed over to RougterDelegater.

Navigator 2.0’s new declarative API consists mainly of the Page and Router sections, which together provide a strong foundation for Navigator 2.0.

Page

Page means “page”, as a widget is a component. Widgets hold component configuration information, and a createElement is built into the framework layer to create a corresponding Element instance. Page also stores information about the Route of the Page, and there is a createRoute method in the framework layer to create a corresponding Route instance.

Both widgets and pages have a canUpdate method that helps Flutter determine whether it has been updated or changed.

// Widget
static bool canUpdate(Widget oldWidget, Widget newWidget) {
  return oldWidget.runtimeType == newWidget.runtimeType
      && oldWidget.key == newWidget.key;
}
// Page
bool canUpdate(Page<dynamic> other) {
  return other.runtimeType == runtimeType &&
         other.key == key;
}
Copy the code

The Page class inherits from the RouteSettings class we used in Navigator 1.0 and holds information including route names and route arguments:

abstract class Page<T> extends RouteSettings
Copy the code

In the new Navigator component, you receive a list of Page objects with the Pages parameter

class _MyAppState extends State<MyApp> { final pages = [ MyPage( key: Key('/'), name: '/', builder: (context) => HomeScreen(), ), ... ] ; @override Widget build(BuildContext context) { return //... Navigator( key: _navigatorKey, pages: List.of(pages), ), } }Copy the code

When Flutter is run, a Route instance corresponding to pages will be generated in the underlying routing stack based on all Page objects in the Pages list, i.e. the routing Page corresponding to pages.

When a Page is opened, a Page object is added to pages. Upon receiving the notification of changes to the upper Pages, the system will compare the new pages with the old pages. Based on the comparison result, a new Route instance will be generated in the underlying routing stack by Flutter. The Navigator component accepts a callback function in response to the page’s POP event via the onPopPage parameter.

Router

The Router inherits from StatefulWidget and can manage its own state. When we change the content or status of the pages, the Router will distribute the status to the sub-components. The status change causes the sub-components to reconstruct the latest status of the application. When the Navigator is a child of the Router, it has the ability to sense changes in route state.

This is also at the heart of the declarative API emphasized by Navigator 2.0, where we no longer manipulate routing as a push or pop, but as an application state change!

A Router manages its status by configuring a RouterDelegate. RouterDelegate is passed in as the RouterDelegate parameter in the materialApp. router constructor. Internally, setInitialRoutePath and setNewRoutePath are used to define initial routes and new routes. Event notification is implemented through the ChangeNotifier mechanism. Navigator component is returned in the build method. Enables Navigator to achieve state awareness just like router.

In application development, the basic function of the Router is to listen for various routing-related events from the system, including:

The initial route requested by the system when the application is first started.

Listen for new intents from the system to open a new routing page.

The listening device falls back and closes the top route in the routing stack.

When an application startup or new page event is sent from the system, it’s forwarded to the application layer with a string representing the event. The RouteNameParser Delegate passes the string to RouteNameParser, which is then parsed into an object of type T. The type T defaults to the RouteSetting, which contains information such as the route name and parameters to be passed.

Similarly, when the user clicks the device back button, the event is passed to the BackButtonDispatcher Delegate.

Both the object data parsed by RouteNameParser and the BackButtonDispatcher Delegate fallback events are forwarded to the RouteDelegate. The RouteDelegate receives these event notifications and executes a response to change the state. This causes the Navigator component with Pages to be rebuilt to present the latest routing status in the application layer.