Flutter's Navigator and AuroRoute

flutterdart

Flutter’s Navigator is a screen transition class by stacking Routes and has APIs such as push() and pop().

Screen transition by Navigator.push()
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Navigation Test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const Page1(),
    );
  }
}

class Page1 extends StatelessWidget {
  const Page1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("page1"),
      ),
      body: ListView.builder(
        itemCount: 10,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(index.toString()),
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => Page2(value: index),
                ),
              );
            }
          );
        }
      )
    );
  }
}

class Page2 extends StatelessWidget {
  const Page2({
    Key? key, 
    required this.value
  }) : super(key: key);

  final int value;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("page2"),
      ),
      body: Center(
        child: Text(
          value.toString(), 
          style: TextStyle(fontSize: value * 10)
        )
      )
    );
  }
}

In the above code, Route is made and passed at the time of push(). On the other hand, as following code if you set it with routes or onGenerateRoute() in MaterialApp, you can make a transition by name like Navigator.pushNamed(context, '/aaa') and make it easy to manage. It also supports deep link so can be opened directly with URL.

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const Page1(),
      routes: <String, WidgetBuilder> {
        '/aaa': (BuildContext context) => const Page2(value: 12),
      }
      // or 
      onGenerateRoute: (settings) {
        switch (settings.name) {
          case '/':
            return MaterialPageRoute(builder: (_) => const Page1());
          case '/aaa':
            return MaterialPageRoute(builder: (_) => const Page2(value: 12));
        }
      }
    );
  }
}

If you take this method, parameters are passed with arguments etc, but there are no type checks so incorrect type values can be passed and it result in Expected a value of type 'int', but got one of the 'String' error at runtime.

onGenerateRoute: (settings) {
  switch (settings.name) {
    case '/':
      return MaterialPageRoute(builder: (_) => const Page1());
    case '/aaa':
      final args = settings.arguments as Map<String, dynamic>;
      final value = args['value'] as int;
      return MaterialPageRoute(builder: (_) => Page2(value: value));
  }
}
      
Navigator.pushNamed(
  context,
  '/aaa',
  arguments: {'value': index}
)

AutoRoute

The library that solves this issue is AutoRoute. It automatically generates the code to pass arguments with type-safe.

dependencies:
  ...
  auto_route: ^3.1.0

dev_dependencies:
  ...
  auto_route_generator: ^3.1.0
  build_runner:

Describe routes in @MaterialAutoRouter and build it then app_router.gr.dart is generated.

$ cat lib/app_router.dart
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'main.dart';

part 'app_router.gr.dart';
  
@MaterialAutoRouter(        
  replaceInRouteName: 'Page,Route',        
  routes: <AutoRoute>[        
    AutoRoute(page: Page1, path: '/', initial: true),        
    AutoRoute(page: Page2, path: '/aaa'),        
  ],        
)        
class AppRouter extends _$AppRouter{}

$ flutter packages pub run build_runner build 

After that, pass the generated AppRouter to MaterialApp.

import 'package:flutter/material.dart';
import 'app_router.dart';
import 'package:auto_route/auto_route.dart';

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);
  
  final _appRouter = AppRouter();  

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(        
        routerDelegate:_appRouter.delegate(),        
        routeInformationParser: _appRouter.defaultRouteParser(),   
        theme: ThemeData(
          primarySwatch: Colors.blue,
        )     
    );
  }
}
...

StackRouter retrived by AutoRouter.of(context) receives PageRouteInfo including the name.

class Page1 extends StatelessWidget {
  const Page1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("page1"),
      ),
      body: ListView.builder(
        itemCount: 10,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(index.toString()),
            onTap: () {
              AutoRouter.of(context).push(Route2(value: 10));
            }
          );
        }
      )
    );
  }
}

References

Understanding Flutter: deep links on the web - Marquee de Sells: Chris’s insight outlet