Routing management

The route in a FLutter, like the native componentized route, is a jump between pages, also known as navigation. App maintains a routing stack, and routing push operation corresponds to opening a new page, while routing pop operation corresponds to closing the page. Routing management mainly refers to how to manage routing stack.

MaterialPageRoute

The MaterialPageRoute is a modal route that can replace an entire screen page with an adaptive transition animation for different platforms:

For Android, when you open a new page, the new page is imported from the bottom of the screen to the top. When you close the page, it slides from the top to the bottom and disappears.

On iOS, the page slides in from the right and exits backwards.

The MaterialPageRoute constructor’s parameters are described below:

  MaterialPageRoute({
    WidgetBuilder builder,
    RouteSettings settings,
    bool maintainState = true,
    bool fullscreenDialog = false,})Copy the code

Builder is a callback function of type WidgetBuilder that builds the details of the routing page and returns a widget. We typically implement this callback to return an instance of the new route. The Settings contains the configuration information of the route, such as the route name and whether the route is an initial route (home page). MaintainState: By default, when a new route is pushed, the original route is still saved in memory. To release all the resources used by the route, you can set maintainState to false. FullscreenDialog indicates whether the new routing page is a full-screen modal dialog. In iOS, if fullscreenDialog is true, the new page will slide in from the bottom of the screen (instead of horizontally).

The basic use

The Flutter provides us with a Navigator. Arguments are passed in to the current BuildContext and the page to navigate.

  1. Call navigator.push to navigate to the second page

     Navigator.push(
               context, new MaterialPageRoute(builder:       (context) => Page2()));
    Copy the code
  2. Call navigator.pop to return to the previous page

     Navigator.pop(context, result);
    
    Copy the code
  3. Sometimes we need to pass in a return value when the previous page was closed. Fortunately, the Navigator calls methods called Future, so we can wait for their results:

    3.1. Wait for Navigator to run

    3.2. Pass the return value to navigator.pop

    3.3. After the wait is complete, obtain the return value

    In page1, navigate to page2, and await the return value to page2 and pop, according to the return value popup different dialog box:

    onPressed: () async {
          var navigationResult = await Navigator.push(
              context, new MaterialPageRoute(builder: (context) => Page2()));
    
          if (navigationResult == 'from_back') {
            showDialog(
                context: context,
                builder: (context) => AlertDialog(
                      title: Text('Navigation from back'))); }else if (navigationResult == 'from_button') {
            showDialog(
                context: context,
                builder: (context) => AlertDialog(
                      title: Text('Navigation from button'))); }},Copy the code

    Pass the return value in page2 and return:

    Navigator.pop(context, 'from_button');
    Copy the code
  4. If you don’t want to click the return key to close the current page, you can use the WillPopScope widget, which covers the scaffolding in the outermost layer. And return false to onWillPop. False Indicates that the current page is not being processed.

       @override
       Widget build(BuildContext context) {
         return WillPopScope(
           onWillPop: () => Future.value(false),
           child: Scaffold(
             body: Container(
               child: Center(
                 child: Text('Page 2', style: TextStyle(fontSize: 30.0, fontWeight: fontweight.bold),),),); }Copy the code

If you want to customize the return key, you can do it yourself before return false, such as closing the current page and passing the return value:

       WillPopScope(
             onWillPop: () async {
                 // You can await in the calling widget for my_value and handle when complete.
                 Navigator.pop(context, 'my_value');
                 return false;
               },
               ...
       );
Copy the code

After routing

The basic use

We can first give a name to the route, and then register the routing table, and then open the new route directly by the route name. This brings an intuitive, simple way for route management, and can be reused.

The Routes property of the MaterialApp is used to register the routing table. It corresponds to a Map<String, WidgetBuilder>.

The name:

  static const String page1 = "/page1";
  static const String page2 = "/page2";
Copy the code

Declare the routing table:

  Map<String, WidgetBuilder> routes = {
    page1: (context) => Page1(),
    page2: (context) => Page2(),
  };
Copy the code

Register routing table:

 @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      routes: routes,
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Page1(),
    );
  }
Copy the code

Then call with the named route where routing is needed:

Navigator.pushNamed(context, page2)
Copy the code

Passing parameters

Name page3:

    page3: (context) => Page3(),

Copy the code

Pass parameters when opening routes:

              Navigator.of(context).pushNamed(page3, arguments: "hi");

Copy the code

Parameters received in page3:

Class Page3 extends StatelessWidget {@override Widget Build (BuildContext Context) {var args = ModalRoute.of(context).settings.arguments;return Scaffold(
      body: Container(
        child: Center(
          child: Text('Page 3 argument is $args', style: TextStyle(fontSize: 30.0, fontWeight: fontweight.bold),),); }}Copy the code

The constructor takes arguments

We passed page3 arguments, but not to the constructor. It’s not a good idea to look at the constructor and not know what arguments are being passed and have to look at the route. So how do you pass the constructor parameters?

The name:

const String page4 = "/page4";

Copy the code

Register route:

    page4: (context) => Page4(text: ModalRoute.of(context).settings.arguments),

Copy the code

Pass parameters when opening routes:

              Navigator.of(context).pushNamed(page4, arguments: "hello");

Copy the code

Dynamic routing

The MaterialApp also provides us with an onGenerateRoute parameter, where routes that are not registered in the routing table will be found. The RouteFactory has a RouteSettings parameter and returns a Route. This is the function we will use to perform all routing.

Route<dynamic> Function(RouteSettings settings)

Copy the code

We can use the routing table as follows:

Route<dynamic> generateRoute(RouteSettings settings) {
	switch (settings.name) {
		case page5:
			return MaterialPageRoute(builder: (context) => Page5());
		case page6:
			return MaterialPageRoute(builder: (context) => Page6());
		default:
			return MaterialPageRoute(builder: (context) => Page1());
	}
Copy the code

Registration:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      routes: routes,
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      onGenerateRoute: generateRoute,
      home: Page1(),
    );
  }
Copy the code

Open route:

              Navigator.of(context).pushNamed(page5);

Copy the code

Dynamic routing delivers parameters

Settings can get parameters, so we can pass parameters:

Route<dynamic> generateRoute(RouteSettings settings) {
	print('====${settings.name}');
	switch (settings.name) {
		case page5:
			return MaterialPageRoute(builder: (context) => Page5());
		case page6:
			return MaterialPageRoute(builder: (context) => Page6(text: settings.arguments,));
		default:
			returnMaterialPageRoute(builder: (context) => Page1()); }}Copy the code

Use:

              Navigator.of(context).pushNamed(page6, arguments: "world");

Copy the code

So easy.

Handle undefined routes

There are two ways to handle undefined routes.

  1. With generateRoute, the default route returned for the route name cannot be found
Route<dynamic> generateRoute(RouteSettings settings) {
	print('====${settings.name}');
	switch (settings.name) {
		case page5:
			return MaterialPageRoute(builder: (context) => Page5());
		case page6:
			return MaterialPageRoute(builder: (context) => Page6(text: settings.arguments,));
		default:
			returnMaterialPageRoute(builder: (context) => NotFindPage()); }}Copy the code
  1. Use onUnknownRoute to return the default route
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        routes: routes,
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        onGenerateRoute: generateRoute,
        onUnknownRoute: (settings) =>
            MaterialPageRoute(builder: (context) => NotFindPage()));
  }
Copy the code

Initial route

There are also two ways to open the route to the first screen of the application,

  1. You can set initialRoute to specify the name of the route registered in the routing table.
  2. You can set the page corresponding to home. InitialRoute overwrites home.
 Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        routes: routes,
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        initialRoute: root,
        home: Page2(),
        onGenerateRoute: generateRoute,
        onUnknownRoute: (settings) =>
            MaterialPageRoute(builder: (context) => NotFindPage()));
  }
Copy the code

Route navigation without BuildContext

In many cases, we have separated the UI code from the business logic (similar to the MVVM architecture). The viewModel should handle all the logic, and the view should only call functions on the model, then rebuild itself with new state as needed.

Do we know that the Navigator needs BuildContext parameters, we are navigating where the actual business logic decision is made, not calling the route in the widget, and passing in the context if we navigate in the viewModel? Let’s implement navigation without context.

To comply with MVVM principles, we will move the Navigation functionality to a service that can be invoked from the viewModel. Create a new folder called services under lib and create a new file called navigation_service.dart in it.

Implement simple profit mode first:

class NavigationService {
  factory NavigationService.getInstance() => _getInstance();

  NavigationService._internal();

  static NavigationService _instance;

  static NavigationService _getInstance() {
    if (_instance == null) {
      _instance = new NavigationService._internal();
    }
    return_instance; }}Copy the code

Then use navigatorKey to implement:

 final GlobalKey<NavigatorState> navigatorKey =
      new GlobalKey<NavigatorState>();

  Future<dynamic> navigateTo(String routeName) {
    return navigatorKey.currentState.pushNamed(routeName);
  }

  void goBack() {
    return navigatorKey.currentState.pop();
  }
Copy the code

We provide the NavigationService and application link to the MaterialApp via navigatorKey. Dart file and set navigatorKey:

 MaterialApp(
        title: 'Flutter Demo',
        navigatorKey: NavigationService().navigatorKey,
        ...
        )
Copy the code

Then write a viewModel and try to navigate:

class ViewModel { final NavigationService _navigationService = NavigationService(); Future goPage1() async{/// await future.delayed (Duration(seconds: 1)); _navigationService.navigateTo(page1); }}Copy the code

Use viewModel navigation in page6:

  onPressed: () {
          viewModel.goPage1();
        },
Copy the code

Instead of showing the UI passing user actions to the model and navigating, you now take the responsibility of the View file back to show the UI and pass them to the model. More consistent with the division of MVVM responsibilities.

The advantage of this is that as the navigation logic expands, our UI stays the same and the model hosts all logic/state management.

Code link