This is the 27th day of my participation in the More Text Challenge. For more details, see more text Challenge

preface

In the previousThe routing of the Flutter 2.0 confused meAn introduction to the routing of the Flutter 2.0 has been given, but it is mostly a blur. Today is a day of juggling (a little exaggeration, the morning in the fish to watch the NBA Sun clippers live), finally a complete example out, a word to sum up is: mountain road 18 bend!As a reminder, this is a long piece of writing that takes time to read (and certainly takes time to walk on mountain roads), and if you’re impatient,Point a praiseIt is also possible to download the source code directly.

The code structure

In order to simplify the understanding, this article has removed the previous redundant demonstration, and only keeps the launch page, dynamic list and dynamic details page. The source code can be seen here. Specifically, there are three types of code:

  • Page code: UI interface code, including the launch page, dynamic list, and dynamic details page
  • Routing code: the 2.0 routing implementation code, including the routing configuration data classAppRouterConfiguration, routing information resolution classAppRouterInformationParserAnd the core routing delegate classAppRouterDelegate.
  • App configuration: Inmain.dartChange the route configuration mode of App entry class MyApp to 2.0 route configuration mode.

The code directory structure is as follows:

2.0 Routing concept

The 2.0 routing changes are more to meet the need for 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 predefined behavior by changing the state. Therefore, the key point of 2.0 routing is that the Navigator. Push or Navigator. Pop methods are missing in the new interface, and the interface only changes the data state in response to user actions.

Routing code interpretation

To simplify code reading, the route configuration code is in the app_Router_path. dart class. The following is defined here:

  • RouterPaths: Page route enumeration, different enumeration corresponding to different pages;
  • AppRouterConfiguration: Route configuration class, a base type that stores the current enumeration of routespath(in order to know the current routing address) and a dynamic status datastate(used to pass data to a new page).
  • AppRouterInformationParser: Route information resolution class, inherited fromRouteInformationParserWhen a route jump is performed, the route resolution method is invoked to obtain the corresponding route configuration object. This class duplicates two methods, one isparseRouteInformationThis method is used to return the corresponding route configuration object after the route path is resolved. anotherrestoreRouteInformationIs to return different routing information objects through different route enumerationsparseRouteInformationThe inverse of theta.

This part of the code is not complex, just read the source code. The tricky part is the route delegate implementation class, defined in Router_delegate.dart. The code for the entire class is as follows:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:home_framework/dynamic_detail.dart';
import 'package:home_framework/models/dynamic_entity.dart';
import 'package:home_framework/not_found.dart';
import 'package:home_framework/routers/app_router_path.dart';
import 'package:home_framework/splash.dart';

import '.. /dynamic.dart';

class AppRouterDelegate extends RouterDelegate<AppRouterConfiguration>
    with
        ChangeNotifier.PopNavigatorRouterDelegateMixin<AppRouterConfiguration> {
  @override
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

  RouterPaths _routerPath;
  get routerPath => _routerPath;
  set routerPath(RouterPaths value) {
    if (_routerPath == value) return;
    _routerPath = value;

    notifyListeners();
  }

  dynamic _state;
  get state => _state;

  bool _splashFinished = false;
  get splashFinished => _splashFinished;

  set splashFinished(bool value) {
    if (_splashFinished == value) return;
    _splashFinished = value;

    notifyListeners();
  }

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: _buildPages(),
      onPopPage: _handlePopPage,
    );
  }

  List<Page<void>> _buildPages() {
    if (_splashFinished) {
      return [
        MaterialPage(
            key: ValueKey('home'),
            child: DynamicPage(_handleDynamicItemChanged)),
        if (_routerPath == RouterPaths.splash)
          MaterialPage(
              key: ValueKey('splash'), child: Splash(_handleSplashFinished)),
        if (_routerPath == RouterPaths.dynamicDetail)
          MaterialPage(
              key: ValueKey('dynamicDetail'), child: DynamicDetail(state)),
        if (_routerPath == RouterPaths.notFound)
          MaterialPage(key: ValueKey('notFound'), child: NotFound()),
      ];
    } else {
      return [
        MaterialPage(
            key: ValueKey('splash'), child: Splash(_handleSplashFinished)), ]; }}void _handleSplashFinished() {
    _routerPath = RouterPaths.dynamicList;
    _splashFinished = true;
    notifyListeners();
  }

  void _handleDynamicItemChanged(DynamicEntity dynamicEntity) {
    _routerPath = RouterPaths.dynamicDetail;
    _state = dynamicEntity;
    notifyListeners();
  }

  @override
  Future<bool> popRoute() async {
    return true;
  }

  @override
  Future<void> setNewRoutePath(AppRouterConfiguration configuration) async {
    _routerPath = configuration.path;
    _state = configuration.state;
  }

  bool _handlePopPage(Route<dynamic> route, dynamic result) {
    final bool success = route.didPop(result);
    return success;
  }

  @override
  AppRouterConfiguration get currentConfiguration =>
      AppRouterConfiguration(routerPath, state);
}

Copy the code

AppRouterDelegate inherits from RouterDelegate

. RouterDelegate itself is a generic class, The generics instantiated with AppRouterConfiguration as the routing configuration class are specified during inheritance.

Used at the same time with the way the ChangeNotifier and PopNavigatorRouterDelegateMixin. The ChangeNotifier is used to add the listener and notify the listener to take actions. This listener is added directly by the underlying layer, and notifyListeners should be called when there is a state change. This is the equivalent of the observer pattern implementation, interested in the ChangeNotifier source code.

PopNavigatorRouterDelegateMixin used to manage returns the event, there is only one method, you can override the method the custom return event.

First look at defining member attributes:

  • navigatorKey: used to store the state of the navigatorGlobalKeySo that the current state of the navigator can be known globally.
  • _routerPath: Stores the enumeration of routes on the current page. When a change occurs, you can notify the route to jump.
  • _state: Route status object (route parameter), and_routerPathTogether, you can build the current routing configurationAppRouterConfigurationObject.
  • _splashFinished: Whether the startup page is complete. When there is a startup page, the home page is the startup page. It is used to remove the startup page from the routing table after the startup is complete, so that the actual home page can be displayed. This is in the back_buildPagesMethods are reflected.

Let’s look at the routing related methods (not including the interface delivery method here) :

  • buildMethod: Route construction method, through aNavigatorPackage all routing pages, something like thatReactThe router (copy no copyReactI don’t know, it just looks like), the first route is the home page, followed by the current route enumeration state to match to and return to the corresponding page. With a return processing method specified, this can be customized for different return scenarios.
  • _buildPagesMethod: for returnbuildMethodpagesParameters. Depending on whether the startup page has completed loading, what page is returned.
  • popRoute: fu wrotePopNavigatorRouterDelegateMixinMethod, which I just did here, just returnedtrue.
  • setNewRoutePath: Sets the route configuration parameters to update the status of the route_routerPath_state.
  • _handlePopPage: that is,buildMethod, again, simple processing here.
  • currentConfiguration2. To get through._routerPathand_stateBuild the current route configuration parameter returned.

The whole process is that when a route configuration parameter changes, the build method is called again to build the page and decide which page to go to.

Business code change

Because the business code can no longer use push and pop jumps and returns, all of these need to be changed, because the route state parameters need to be modified, so the behavior of changing the state is done by passing the corresponding callback method when building the business page. The method for starting the end of the page is _handleSplashFinished. After the page is started, the status _splashFinished is set to true and the current route page is changed to the dynamic list. The _handleSplashFinished method is passed to the launch page and is called when the launch time is up to replace the push method, thus realizing the page switch.

class Splash extends StatefulWidget {
  final Function onFinished;
  Splash(this.onFinished, {Key key}) : super(key: key);

  @override
  _SplashState createState() => _SplashState(onFinished);
}

class _SplashState extends State<Splash> {
  final Function onFinished;
  _SplashState(this.onFinished);
  bool _initialized = false;
  
  // Omit the other code
  
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if(! _initialized) { _initialized =true;
      Timer(const Duration(milliseconds: 2000), () { onFinished(); }); }}}Copy the code

Dynamic lists, too, need to receive an onItemTapped method that responds to the click event of each row element and sends the clicked element object back to update the routing parameters. There is also a case where the route passes the function to the dynamic list, and the dynamic list passes the function to each element in the row. The actual purpose of each business code to receive a callback function is to change the route state parameter to achieve a page jump.

You can also see here that the current approach actually exposes the implementation of the business, breaks encapsulation, and leads to a long delivery link if parent and child elements are nested too deeply. At this point, as with React, you need a redux-like state manager to decouple.

The App route configuration is changed

It is relatively easy to change the App route configuration. You can return the MaterialApp. Router method in the build method of the entry to build the App. Just set the two parameters to an implementation object of the corresponding class that I defined. The source code is as follows:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: '2.0 routing',
      routerDelegate: AppRouterDelegate(),
      routeInformationParser: AppRouterInformationParser(),
      // Omit the other code); }}Copy the code

conclusion

Overall, the routing management of Flutter 2.0 is much more complex than that of version 1.0, and for non-Web applications the routing of 1.0 can continue. Of course, the upgrade also has the following advantages:

  • Route management is separate from route resolution. You can define your own route resolution class and route parameter configuration class, which is more flexible.
  • Routing pages can be generated dynamically, making it easier to implement dynamic routing.
  • The page does not need to manage the jump logic. The page and route are separated and decoupled to maintain the consistency of the state-driven interface.
  • A state management component can be introduced to manage the routing state of the entire App, which is more scalable.

Finally, you are welcome to like and interact in the comment section!