🌟

【Flutter】go_router利用時、ルーティング定義を分割・切り替えるサンプル

2023/02/27に公開

背景

go_router を利用して、アプリ作成しています。
ルーティング定義が肥大化する懸念があり、
ルーティング定義を分割・切り替え可能か試しました。
結果、分割・切り替え可能だったため、そのサンプルを記載します。
より具体的には、認証前後で、ルーティング定義を分割・切り替えしました。

分割したルーティング定義の切り替え

分割したルーティングの切り替えについて、図解します。

1.認証前後でルーティング定義を切り替える

認証用レイヤ(AuthLayer)となる Widget を用意しました。
この認証用レイヤの中で、分割したルーティング定義を切り替えました。
分割したルーティング定義は下記です。

  • 認証前(未認証)のルーティング定義:AuthRoutingWidget
  • 認証後(認証済)のルーティング定義:MenuRoutingWidget

auth_layer

2.Widget Inspector で見るルーティング定義の切り替え

auth_layer

3.分割したルーティング定義の比較

auth_layer

応用

このサンプルでは、認証前後でルーティングの定義を分割・切り替えしました。
今後の応用として 「排他的な状態」毎にルーティングの定義を分割・切り替える。
といった設計の選択肢が持てるのではと考えています。

サンプルコード全体

サンプルコード全体

認証用レイヤ

lib/main.dart
import 'package:flutter/material.dart';

import 'auth_layer.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const AuthLayer();
  }
}
lib/auth_layer.dart
import 'package:flutter/material.dart';

import 'auth_routing_widget.dart';
import 'menu_routing_widget.dart';

class AuthLayer extends StatefulWidget {
  const AuthLayer({super.key});

  
  State<AuthLayer> createState() => _AuthLayerState();
}

class _AuthLayerState extends State<AuthLayer> {
  // --------------------------------------------------
  // Function : SignIn, SignOut
  // -----------------------------
  bool isSignedIn = false;

  void onSignIn() {
    setState(() => isSignedIn = true);
  }

  void onSignOut() {
    setState(() => isSignedIn = false);
  }
  // --------------------------------------------------

  
  Widget build(BuildContext context) {
    return isSignedIn
        ? MenuRoutingWidget(onSignOut: onSignOut)
        : AuthRoutingWidget(onSignIn: onSignIn);
  }
}

分割したルーティング定義

lib/auth_routing_widget.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

import 'signin_page.dart';
import 'signup_page.dart';

class AuthRoutingWidget extends StatelessWidget {
  AuthRoutingWidget({
    super.key,
    required this.onSignIn,
  });

  final VoidCallback onSignIn;

  late final goRouter = GoRouter(
    initialLocation: '/signin',
    routes: [
      GoRoute(
        path: '/signin',
        builder: (context, state) => SignInPage(onSignIn: onSignIn),
        routes: [
          GoRoute(
            path: 'signup',
            builder: (context, state) => const SignUpPage(),
          ),
        ],
      ),
    ],
  );

  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      debugShowCheckedModeBanner: false,
      routerConfig: goRouter,
      builder: (context, child) {
        return child ?? const Text('No child');
      },
    );
  }
}
lib/menu_routing_widget.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

import 'article_detail_page.dart';
import 'article_page.dart';

class MenuRoutingWidget extends StatelessWidget {
  MenuRoutingWidget({
    super.key,
    required this.onSignOut,
  });

  final VoidCallback onSignOut;

  late final goRouter = GoRouter(
    initialLocation: '/article',
    routes: [
      GoRoute(
          path: '/article',
          builder: (context, state) => ArticlePage(onSignOut: onSignOut),
          routes: [
            GoRoute(
              path: 'articledetail/:id',
              builder: (context, state) =>
                  ArticleDetailPage(id: state.params['id'] ?? ''),
            ),
          ]),
    ],
  );

  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      debugShowCheckedModeBanner: false,
      routerConfig: goRouter,
      builder: (context, child) {
        return child ?? const Text('No child');
      },
    );
  }
}

認証前ページ ( SignIn, SignUp )

lib/signin_page.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class SignInPage extends StatelessWidget {
  const SignInPage({
    super.key,
    this.title = 'signin',
    required this.onSignIn,
  });

  final String title;
  final VoidCallback onSignIn;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('$title page'),
        actions: [
          TextButton(
            child: const Text(
              'SignUp > ',
              style: TextStyle(color: Colors.white),
            ),
            onPressed: () {
              context.go('/signin/signup');
            },
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('body: $title'),
            ElevatedButton(
              onPressed: () => onSignIn(),
              child: const Text('SignIn'),
            ),
          ],
        ),
      ),
    );
  }
}
lib/signup_page.dart
import 'package:flutter/material.dart';

class SignUpPage extends StatelessWidget {
  const SignUpPage({super.key, this.title = 'signup'});

  final String title;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('$title page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('body: $title'),
          ],
        ),
      ),
    );
  }
}

認証後ページ( Article, ArticleDtail )

lib/article_page.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class ArticlePage extends StatelessWidget {
  const ArticlePage({
    super.key,
    this.title = 'article',
    required this.onSignOut,
  });

  final String title;
  final VoidCallback onSignOut;

  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('$title page'),
          actions: [
            TextButton(
              onPressed: () => onSignOut(),
              child: const Text(
                'SignOut > ',
                style: TextStyle(color: Colors.white),
              ),
            ),
          ],
        ),
        body: ListView.builder(
            itemCount: 3,
            itemBuilder: ((context, index) {
              return ListTile(
                title: Text('Article $index'),
                onTap: () {
                  context.go('/article/articledetail/$index');
                },
              );
            })));
  }
}
lib/article_detail_page.dart
import 'package:flutter/material.dart';

class ArticleDetailPage extends StatelessWidget {
  const ArticleDetailPage({
    super.key,
    this.title = 'article deltail',
    required this.id,
  });

  final String title;
  final String id;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('$title page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('body: $title, id: $id'),
          ],
        ),
      ),
    );
  }
}

関連記事

本記事の続編を書きました。
https://zenn.dev/konbu33/articles/a499cc6cb6f6b3

Discussion