Flutter: go_router で共通の親ウィジェット・処理を持たせたい
December 7, 2022
次の要件があるとする。
「ユーザーのネットワーク接続状態を監視しておき、インターネット未接続状態であればスナックバーでそのことを通知する。」
まず単純に共通の親ウィジェット・処理を持たせるのであれば、以下のように MaterialApp.router()
を囲むウィジェットを定義すれば実現できる。
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MyRootWidget(
child: MaterialApp.router(
title: 'Example App',
routerDelegate: router.routerDelegate,
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
),
);
}
}
class MyRootWidget extends StatefulWidget {
const MyRootWidget({required this.child, super.key});
@override
State<MyRootWidget> createState() => _MyRootWidgetState();
final Widget child;
}
class _MyRootWidgetState extends State<MyRootWidget> {
@override
void initState() {
super.initState();
//
// 必要な処理を実施
//
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
今回はスナックバーを表示させたいのだが、いまの作りだと MyRootWidget は MaterialApp の外側に配置しているため ScaffoldMessenger.of(context).showSnackBar();
するとエラーになってしまう。
対応方法1:go_router の ShellRoute を利用する #
すべてのページの ShellRoute として共通のウィジェットを設定する。こうすれば MaterialApp の内側に配置されるため ScaffoldMessenger.of(context)
はエラーにならない。
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Example App',
routerDelegate: router.routerDelegate,
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
);
}
}
final GoRouter router = GoRouter(
initialLocation: '/home',
routes: <RouteBase>[
// 👇 ShellRoute
ShellRoute(
builder: (BuildContext context, GoRouterState state, Widget child) {
return ShellRouteWidget(child: child);
},
routes: <RouteBase>[
GoRoute(
path: '/home',
pageBuilder: (_, GoRouterState state) => const NoTransitionPage<void>(
child: HomePage(),
),
),
GoRoute(
path: '/setting',
pageBuilder: (_, GoRouterState state) => const NoTransitionPage<void>(
child: SettingPage(),
),
),
],
),
],
);
class ShellRouteWidget extends StatefulWidget {
const ShellRouteWidget({required this.child, super.key});
@override
State<ShellRouteWidget> createState() => _ShellRouteWidgetState();
final Widget child;
}
class _ShellRouteWidgetState extends State<ShellRouteWidget> {
@override
void initState() {
super.initState();
//
// 必要な処理を実施
//
// スナックバーを開くには、
ScaffoldMessenger.of(context).showSnackBar(...);
//
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
対応方法2:MaterialApp の scaffoldMessengerKey を利用する #
グローバルな scaffoldMessengerKey を生成して MaterialApp に設定する。こうすれば MaterialApp 配下のウィジェットでなくても MaterialApp にアクセス可能となる。
final GlobalKey<ScaffoldMessengerState> scaffoldKey = GlobalKey<ScaffoldMessengerState>();
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MyRootWidget(
child: MaterialApp.router(
// 👇 scaffoldMessengerKey
scaffoldMessengerKey: scaffoldKey,
title: 'Example App',
routerDelegate: router.routerDelegate,
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
),
);
}
}
class MyRootWidget extends StatefulWidget {
const MyRootWidget({required this.child, super.key});
@override
State<MyRootWidget> createState() => _MyRootWidgetState();
final Widget child;
}
class _MyRootWidgetState extends State<MyRootWidget> {
@override
void initState() {
super.initState();
//
// 必要な処理を実施
//
// スナックバーを開くには、
final ScaffoldMessengerState? scaffoldMessangerState = scaffoldKey.currentState;
scaffoldMessangerState?.showSnackBar(...);
//
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}