FlutterのNavigatorとAuroRoute

flutterdart

FlutterのNavigatorRouteをスタックし画面遷移させるクラスで、push()pop()といったAPIを提供する。

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)
        )
      )
    );
  }
}

上の例ではpush時にRouteを作って渡しているが、MaterialApproutesonGenerateRoute()で設定すると一箇所にまとまり Navigator.pushNamed(context, '/aaa') のように名前で遷移することができる。 また、ディープリンクにも対応し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));
        }
      }
    );
  }
}

この方法を取るとargumentsなどで値を受け渡すことになるが、型の制約がないため誤った型の値を渡すことができ、実行時に Expected a value of type 'int', but got one of the 'String' のようなエラーが出てしまう。

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

これを解決するライブラリがAutoRouteで、argumentsをtype-safeに渡すためのコードを自動生成してくれる。

dependencies:
  ...
  auto_route: ^3.1.0

dev_dependencies:
  ...
  auto_route_generator: ^3.1.0
  build_runner:

@MaterialAutoRouterにRouteを記述しbuildするとapp_router.gr.dartが生成される。

$ 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 

あとは生成されたAppRouterMaterialAppに渡せば準備完了。

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,
        )     
    );
  }
}
...

AutoRouter.of(context)で取得したStackRouterに、名前を含むPageRouteInfoを渡せば画面遷移できる。

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));
            }
          );
        }
      )
    );
  }
}

参考

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