Closed12

Page に route() メソッド用意する設計について

へぶんへぶん

チームでも話題になってたこともあり、ashdik(朝日) さんの「【Flutter】もうnamedRouteは使わない!僕が全力で勧めたいルーティング方法をサンプル付きで解説してみた」を読んだ。
https://blog.dalt.me/2616

へぶんへぶん

ページを追加するたびに、routeNameを定義して、
app.dartに移動して、routesに追加して…とやるのが大変億劫でした。

理に適ってるなと思った。

ただ、個人的には namedRoute で嫌なのは、Flutter の Navigator の仕組み上 arguments を Object 型で渡さないといけないことだったから、そこだけいい感じに変えたいと思った。
https://api.flutter.dev/flutter/widgets/Navigator/pushNamed.html

へぶんへぶん

arguments で渡すような「ページの引数」にあたる情報は、route メソッドの引数として受けて直接ページクラスのコンストラクタに渡せば良さそう。

class NextPage extends StatelessWidget {
  NextPage._({
     this.index,
    Key key,
  }) : super(key: key);

  // フィールドとして持っとく。
  final int index;

  static PageRoute<void> route({
     int index,
  }) {
    return MaterialPageRoute(
      builder: (ctx) => NextPage._(
        index: index,
      ),
    );
  }

  
  Widget build(BuildContext context) {
      ...(省略)
  }
}

へぶんへぶん

呼び出し側はこう

Navigator.of(context).push(
  NextPage.route(index: 0),
);
へぶんへぶん

課題感としては、記事中で言うと以下のコードの someId が Object 型になっちゃうところでした。

    final someId = ModalRoute.of(context).settings.arguments;
ashdikashdik

(おぉ、スクラップと言う使い方が!)
この課題感めっちゃ分かります、
それもこの書き方を辞めたかった理由の一つなので👍

ashdikashdik

そして、メンバ変数として持つの全く問題ないと思ってますし
なんだったらこれ全く同じこと僕もやってます👍

へぶんへぶん

良かったです👍

(スクラップの使い方わかってないですけど、意見もらいたい時スクラップいいかも)

へぶんへぶん

あとは、firebase_analyticsFirebaseAnalyticsObserver にスクリーン名をログ取ってもらうために、RouteSettings も定義してあげればいいかな。

class NextPage extends StatelessWidget {
  static PageRoute<void> route({
     int index,
  }) {
    return MaterialPageRoute(
      builder: (ctx) => NextPage._(
        index: index,
      ),
      settings: RouteSettings(name: '/next'), // <= It's for Navigator#observers
    );
  }

  
  Widget build(BuildContext context) {
      ...(省略)
  }
}

へぶんへぶん

フルコードはこんな感じ。

import 'package:flutter/material.dart';

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),

      /// Using [Builder] for sample code.
      home: Builder(
        builder: (context) {
          return Scaffold(
            body: Center(
              child: RaisedButton(
                onPressed: () {
                  Navigator.of(context).push(
                    NextPage.route(index: 0),
                  );
                },
                child: Text('Navigate to next page'),
              ),
            ),
          );
        },
      ),
    );
  }
}

class NextPage extends StatelessWidget {
  NextPage._({
     this.index,
    Key key,
  }) : super(key: key);

  final int index;

  static PageRoute<void> route({
     int index,
  }) {
    return MaterialPageRoute(
      builder: (ctx) => NextPage._(
        index: index,
      ),
      settings: RouteSettings(name: '/next'),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('No.$index'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('No.$index'),
            RaisedButton(
              onPressed: () {
                Navigator.of(context).push(
                  NextPage.route(index: index + 1),
                );
              },
              child: Text('Navigate to next page'),
            ),
          ],
        ),
      ),
    );
  }
}

へぶんへぶん

kikuchy さんにフィードバックもらった。

確かに、運用上 screenName 忘れて Firebase Analytics の不整合が起きるの面倒そう。

ただ、同ページに複数の route メソッド用意したいケースもある気がするので、自動生成で対応するの面倒かも。

今思いつくのは、PageRoute 作成用の factory を用意して、screenName を required パラメータにすることくらいかな。

class PageRouteBuilder {
  static MaterialPageRoute<T> buildMaterialRoute<T>({
     WidgetBuilder builder,
     String name,
    bool fullscreenDialog = false,
  }) {
    return MaterialPageRoute<T>(
      builder: builder,
      settings: RouteSettings(name: name),
      fullscreenDialog: fullscreenDialog,
    );
  }
}
このスクラップは2021/01/05にクローズされました