LoginSignup
81
51

More than 3 years have passed since last update.

【Flutter】MaterialAppにて最初に起動させたいページの設定方法について

Last updated at Posted at 2020-01-05

結論

MaterialAppを使ってるアプリにおいて、最初に起動させたいページを設定するには、以下の2つの方法がある。

  • homeプロパティに記述する
entry.dart
    return MaterialApp(
      title: "NavigatorApp",
      home: SplashPage(),               // 1. 最初に起動させたいページ
      routes: <String, WidgetBuilder>{  // 2. routesには「/」を含めない。
        '/welcome': (BuildContext context) => WelcomePage(),
        '/home': (BuildContext context) => HomePage(),
        '/settings': (BuildContext context) => SettingsPage(),
      },
    );
  • routesプロパティまたは、onGenerateRoutes内で「/」のエントリを定義する。
entry.dart
    return MaterialApp(
      title: "NavigatorApp",
      routes: <String, WidgetBuilder>{
        '/': (BuildContext context) => SplashPage(),         // 1. 最初に起動させたいページを「/」で定義
        '/welcome': (BuildContext context) => WelcomePage(),
        '/home': (BuildContext context) => HomePage(),
        '/settings': (BuildContext context) => SettingsPage(),
      },
    );

背景

なんでこの記事を書いたかというとFlutterのRouteの設定周りで少しハマったためです。その時の話を以下に紹介します。

FlutterではMatertialAppというウィジェットがあり、基本アプリのトップレベルで定義することが多いと思います。書き方としては、buildメソッドの中で、以下のような定義をすると思います。

entry.dart
    return MaterialApp(
      title: "NavigatorApp",
      initialRoute: '/',
      routes: <String, WidgetBuilder>{
        '/': (BuildContext context) => HomePage(),
        '/settings': (BuildContext context) => SettingsPage(),
      },
    );

ここで大事な部分は、「initialRoute」というプロパティと、「routes」というプロパティです。

routes

名前付きルートを使って遷移する場合に使う識別子(文字列)をキーとし、対応するウィジェットを値としてMapで記述する。

initialRoute

アプリを起動した際に最初に遷移されるRouteの識別子(だと思ってた)

何が問題だったか

例えば、ログイン状態の有無によって、アプリの初期起動時に表示されるページを切り替えたい場合があるとします。その場合、Proxyとなるページ(/splash)を最初に起動させ、認証状態を確認し、ログイン済みであれば、「ホーム画面(/)」、ログインしていなければ「Welcome画面(/welcome)」に遷移させるということをすると思います。

これをやろうとした場合最初以下のように定義しました。initialRouteにProxyとなる「/splash」というルートを設定すれば、最初は、/splashのページが起動することができるだろうと。。

entry.dart
    return MaterialApp(
      title: "NavigatorApp",
      initialRoute: '/splash',
      routes: <String, WidgetBuilder>{
        '/splash': (BuildContext context) => Provider<SplashBloc>(  // Providerを使って、Blocを注入する想定
              builder: (context) => SplashBloc(),
              child: SplashPage(),
              dispose: (_, bloc) {
                bloc.dispose();
              },
            ),
        '/welcome': (BuildContext context) => WelcomePage(),
        '/': (BuildContext context) => HomePage(),
        '/settings': (BuildContext context) => SettingsPage(),
      },
    );

なぜかinitialRouteの「/splash」が無視され、ホーム画面(/)が表示される

そうなんです。なぜか上記で設定した「/splash」のルートが起動されず、無視された挙句「/」のルートが起動されました。

原因調査

MaterialAppの実装を見てみると、以下のように書いてありました。

/// At least one of [home], [routes], [onGenerateRoute], or [builder] must be
/// non-null. If only [routes] is given, it must include an entry for the
/// [Navigator.defaultRouteName](/), since that is the route used when the
/// application is launched with an intent that specifies an otherwise
/// unsupported route.

home, routes, onGenerateRoute, builderの中で少なくとも一つはnon-nullでなくてはならない。routeが与えられた時、そのrouteはアプリケーションが起動される時(割愛)に使われるので、Navigator.defaultRouteName/)のためのエントリーを含んでいる必要があります。

つまり、homeプロパティが設定されていない場合は、routesの中からエントリが決まり、「/」に対応するエントリ(ページ)を含んでいる必要があるということです。

また、次のようにも書いてありました。

/// The [MaterialApp] configures the top-level [Navigator] to search for routes
/// in the following order:
///
/// 1. For the / route, the [home] property, if non-null, is used.
///
/// 2. Otherwise, the [routes] table is used, if it has an entry for the route.
///
/// 3. Otherwise, [onGenerateRoute] is called, if provided. It should return a
/// non-null value for any valid route not handled by [home] and [routes].
///
/// 4. Finally if all else fails [onUnknownRoute] is called.

MaterialAppはRouteの探索のため、以下の順序にしたがって、トップレベルのNavigatorを設定します。
1. homeプロパティがnullでなければ、「/」のルートとしてそれが使われる。
2. それ以外でroutesテーブル(プロパティ)に「/」のエントリがあれば、それが使われる
3,4. ~省略~

つまり、今回の場合homeプロパティは指定されていないかつ、routesプロパティで「/」のエントリが設定されていたため、暗黙的に「/」のエントリが使われてしまったようです。(え、initialRouteなんのためにあるの、、)

修正後

ということで以下のようにinitialRouteを削除し、routesプロパティに「/」としてSplashPageを設定するように修正したところ意図した通りに動きました。

entry.dart
    return MaterialApp(
      title: "NavigatorApp",
      routes: <String, WidgetBuilder>{
        '/': (BuildContext context) => Provider<SplashBloc>(  // '/'をSplashPageにする
              builder: (context) => SplashBloc(),
              child: SplashPage(),
              dispose: (_, bloc) {
                bloc.dispose();
              },
            ),
        '/welcome': (BuildContext context) => WelcomePage(),
        '/home': (BuildContext context) => HomePage(),
        '/settings': (BuildContext context) => SettingsPage(),
      },
    );

また、以下のようにhomeプロパティにSplashPageを定義しても良さそうです。

entry.dart
    return MaterialApp(
      title: "NavigatorApp",
      home: Provider<SplashBloc>(  // '/'をSplashPageにする
              builder: (context) => SplashBloc(),
              child: SplashPage(),
              dispose: (_, bloc) {
                bloc.dispose();
              },
            ),
      routes: <String, WidgetBuilder>{
        '/welcome': (BuildContext context) => WelcomePage(),
        '/home': (BuildContext context) => HomePage(),
        '/settings': (BuildContext context) => SettingsPage(),
      },
    );

まとめ

Navigator周りですが、ロジックが意外と複雑だったので、理解しておくと後でハマることがないので良さそうです。
また、initialRouteは今の所設定する理由がなさそうに見えますが、多分他に用途があると思うので、もう少し理解を深めていきたいです。
最後まで読んでいただきありがとうございました。

81
51
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
81
51