🐽

画面遷移のボタンをコンポーネント化したい

2023/04/06に公開

どこまでできる?

画面遷移のコードを短くかける方法を試したことはあるけど、ロジックだけではなくボタンまで、使い回せるようにする方法は試したことがなかった!

以前こんな記事を書いたことがあります🧑‍💻
https://zenn.dev/joo_hashi/articles/e741bc068e2f7c

でも、go_routerを使うのが最近はマストになってきたので、コンポーネント化したボタンでもgo_routerで画面遷移できるか実験してみました。

画面遷移のボタンをコンポーネント化する

画面遷移に使うボタンのWidgetを切り分けてコンポーネント化してみた。やることは、
ボタンの名前と画面遷移するWidgetクラスを引数として渡すだけ。

何故コンポーネントするのか?

  • 長いコードを書かずにすむ.
  • 画面遷移するだけなら使いませる

このようなコンポーネントを作る

class _PushButton extends StatelessWidget {
  const _PushButton({
    super.key,
    required this.buttonTitle,
    required this.pageName,
  });

  final String buttonTitle; // ボタンのタイトルを引数として渡す
  final Widget pageName; // 画面遷移先のWidgetクラスを引数として渡す

  
  Widget build(BuildContext context) {
    return ElevatedButton(
        onPressed: () {
          Navigator.of(context)
              .push(MaterialPageRoute(builder: (context) => pageName));
        },
        child: Text(buttonTitle));
  }
}

使用例

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: const <Widget>[
            PushButton(buttonTitle: 'Next Page', pageName: NextPage()),
            SizedBox(height: 20),
            PushButton(buttonTitle: 'MyPage', pageName: MyPage()),
          ],
        ),
      ),
    );
  }
}

コンポーネント化したボタンを呼び出すだけで、書くコードの量を減らせましたね。次はgo_routerを使ってみましょう。

パッケージを追加する

flutter pub add go_router

コードを修正して使ってみる

修正といってもルートのパスをString型の文字として、コンストラクター(初期値)として使えるようにしたぐらいですね。画面遷移のコードは、go_routerに変更してあります。
名前付きルートと呼ばれているものあるのですが、公式によると非推奨なので使うべきかと思い、今回はパスを指定する方法にしました。
https://docs.flutter.dev/development/ui/navigation/deep-linking

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

class PushButton extends StatelessWidget {
  const PushButton({
    super.key,
    required this.buttonTitle,
    required this.rootPath,
  });

  final String buttonTitle; // ボタンのタイトルを引数として渡す
  // final Widget pageName; // 画面遷移先のWidgetクラスを引数として渡す
  final String rootPath; // パスを文字として渡す

  
  Widget build(BuildContext context) {
    return ElevatedButton(
        onPressed: () {
          // Navigator.of(context)
          //     .push(MaterialPageRoute(builder: (context) => pageName));
          GoRouter.of(context).go(rootPath);
        },
        child: Text(buttonTitle));
  }
}

ルートを定義する

go_routerの画面遷移の設定を定義する。全てトップレベルのパスなので、文字の先頭に / をつける。

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:navigator_widget/main.dart';
import 'package:navigator_widget/ui/mypage.dart';
import 'package:navigator_widget/ui/next_page.dart';

/// The route configuration.
final GoRouter goRouter = GoRouter(
  routes: <RouteBase>[
    GoRoute(
      path: '/',
      builder: (BuildContext context, GoRouterState state) {
        return const MyHomePage();
      },
    ),
    GoRoute(
      path: '/next',
      builder: (BuildContext context, GoRouterState state) {
        return const NextPage();
      },
    ),
    GoRoute(
      path: '/my_page',
      builder: (BuildContext context, GoRouterState state) {
        return const MyPage();
      },
    ),
  ],
);

使うときは、こんな風にボタンの名前とルートのパスを引数に渡してあげるだけです。

import 'package:flutter/material.dart';
import 'package:navigator_widget/components/push_button.dart';
import 'package:navigator_widget/root.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: goRouter,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
    );
  }
}

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

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('MyHome Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: const [
            // PushButton(buttonTitle: 'Next Page', pageName: NextPage()),
            // SizedBox(height: 20),
            // PushButton(buttonTitle: 'MyPage', pageName: MyPage()),
            PushButton(buttonTitle: 'Go Next', rootPath: '/next'),
            SizedBox(height: 20),
            PushButton(buttonTitle: 'Go MyPage', rootPath: '/my_page'),
          ],
        ),
      ),
    );
  }
}

画面遷移先のコード

戻るボタンがついているだけ。。。

import 'package:flutter/material.dart';
import 'package:navigator_widget/components/push_button.dart';

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Next Page'),
      ),
      body: Center(
          child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: const [
          PushButton(buttonTitle: 'Back', rootPath: '/'),
        ],
      )),
    );
  }
}
import 'package:flutter/material.dart';
import 'package:navigator_widget/components/push_button.dart';

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('MyPage'),
      ),
      body: Center(
          child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: const [
          PushButton(buttonTitle: 'Back', rootPath: '/'),
        ],
      )),
    );
  }
}

まとめ

なぜか、ネストしたルートでパスを定義すると、ビルドするときにエラーが発生してしまいました!
もしかしたら、無理してWidgetをコンポーネント化するのは良くないのかもしれないです🤔

こちらが完成品のソースコードです。何かお役立つと良いですが。。。
https://github.com/sakurakotubaki/PushButtonApp

Discussion