結論
MaterialAppを使ってるアプリにおいて、最初に起動させたいページを設定するには、以下の2つの方法がある。
- homeプロパティに記述する
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内で「/」のエントリを定義する。
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メソッドの中で、以下のような定義をすると思います。
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のページが起動することができるだろうと。。
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を設定します。
- homeプロパティがnullでなければ、「/」のルートとしてそれが使われる。
- それ以外で
routes
テーブル(プロパティ)に「/」のエントリがあれば、それが使われる
3,4. ~省略~
つまり、今回の場合homeプロパティは指定されていないかつ、routesプロパティで「/」のエントリが設定されていたため、暗黙的に「/」のエントリが使われてしまったようです。(え、initialRouteなんのためにあるの、、)
修正後
ということで以下のようにinitialRouteを削除し、routesプロパティに「/」としてSplashPageを設定するように修正したところ意図した通りに動きました。
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を定義しても良さそうです。
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は今の所設定する理由がなさそうに見えますが、多分他に用途があると思うので、もう少し理解を深めていきたいです。
最後まで読んでいただきありがとうございました。