🏃

Flutterにおける画面遷移の考え方

2024/10/16に公開

はじめに

自分自身はWebエンジニア出身でFlutterでの開発経験はほぼ皆無なのですが、個人的に最近Flutterの勉強を始めています。

今回はFlutterで画面遷移を実装する際の考え方をまとめてみました。

モバイルアプリにおける画面遷移

モバイルアプリの画面遷移はスタック遷移・モーダルビュー・タブバーの3つがあります。

スタック遷移はiOSとAndroidでは考え方が違い、iOSではプッシュ遷移と呼ばれており、
一つのタスクに向かって階層を掘っていくような遷移方法になります。Androidの方は画面の上に別の画面が上に乗っかっていくイメージです。

基本の画面遷移はスタック型の遷移を使い、タブバーとモーダルビューを組み合わせる形になります。iOSとAndroidのUIの微妙な違いはこの記事がわかりやすかったです。
https://note.com/ku_marin/n/n60ebdb19ebd0#9A50M

Flutterにおけるスタック遷移(iOSのプッシュ遷移)

Flutterにおけるスタック遷移には従来のNavigator(Navigator1)と2020年末に登場したRouter(Navigator2)の2つの方法があります。

従来のNavigatorでは直接遷移先を指定する方法(Using the Navigator)と、名前付きルート(Using named routes)を使う方法があります。

Using the Navigator(Navigator1)

Flutterにおける一番基本的な画面遷移の実装方法です
https://docs.flutter.dev/ui/navigation#using-the-navigator

Navigator.of(context).push の処理で新しいルートをナビゲーションスタックにプッシュしています。MaterialPageRouteは、Material Designのアニメーションとスタイルを使用して新しい画面(ルート)への遷移を定義するクラスです。

プラットフォームの挙動に従ったアニメーションが自動で使われます。
iOS: 右からスライドインするアニメーション
Android: 下から上へのフェードインアニメーション

onPressed: () {
  Navigator.of(context).push(
    MaterialPageRoute(
      builder: (context) => const SongScreen(song: song),
    ),
  );
},
child: Text(song.name),

CupertinoPageRouteというiOS風のアニメーションを実現するクラスも用意されていて、iOS特有のスワイプで前ページに戻る挙動を実現したくなった時に使ったりします。

ちなみにこの方法での遷移は記述量も多く、同じようなコードが増えることになってしまうため、
小規模なアプリ以外だと保守が厳しくなりそうです。

Using named routes(Navigator1)

以下のようにパスに名前をつけておくと、Navigator.pushNamedで遷移することができます。
https://docs.flutter.dev/ui/navigation#using-named-routes

公式では以下の2つの利用からオススメはされていませんが、Deep Linkを使っていなかったり、Webアプリ以外のアプリの場合は対象にはならないので、そこまで気にする必要はないかなと思います。

  • Deep Linkがサポートされているが、必ずDeep Linkで指定されたpathに遷移してしまう
  • Webアプリケーションにおいて、ブラウザ上で一度戻るボタンを押した後に、進むボタンを押すことができない

Widget build(BuildContext context) {
  return MaterialApp(
    routes: {
      '/': (context) => HomeScreen(),
      '/details': (context) => DetailScreen(),
    },
  );
}

// 使う時
Navigator.pushNamed(context, '/details');

名前付きルートを使った時のデメリットとしては、パスが文字列なので型の補完が効かないことと、パスの間違いを起こしがちだったりすることです。その問題に関してはこちらの記事のように遷移先のページのメンバ変数でpathを指定してあげる方法は良さそうでした。
https://qiita.com/0maru/items/a82c502a5bad71bf2eed

onGenerateRouteを使った方法

このパターンではURLの値などで動的にnamed routesのようなルーティングを実現できます。

MaterialApp(
  onGenerateRoute: (settings) {    
    // 商品詳細ページ
    if (uri.path.startsWith('/product/')) {
      final productId = uri.pathSegments[1];
      return MaterialPageRoute(
        builder: (_) => ProductDetailPage(productId: productId),
      );
    }

# 使う時
Navigator.pushNamed(context, ProductDetailPage.path, arguments: productId);

Using the Router(Navigator2)

Navigator2はgo_routerというパッケージを使うと楽に使うことができ、go_routerではパスを宣言的に記述します。ネストされたルーティングも以下のように直感的に記述できます。

GoRoute(
  path: '/account',
  builder: (context, state) => const AccountPage(),
  routes: [
    GoRoute(
      path: 'profile',
      builder: (context, state) => const ProfilePage(),
    ),
    GoRoute(
      path: 'settings',
      builder: (context, state) => const SettingsPage(),
    ),
  ],
),

Navigator1では苦労する部分をいい感じにしてくれていそうで、今では最初からgo_routerを使っておいた方が保守性が高そうだなと感じました。この記事はわかりやすかったです。
https://zenn.dev/k_kawasaki/articles/2cee32fc8a907d

さいごに

今回はFlutterにおける画面遷移の考え方についてまとめてみました。基本はgo_routerに実装を統一しつつ、go_routerでは対応が難しいケースのみ、別の方法を検討するのが良さそうですね。

Discussion