🛵

【Flutter】go / push / replace の使い分け徹底解説:go_router遷移の基本と実践

に公開

はじめに

画面遷移を管理するルーティングパッケージである go_routergo_router_builder を使ったルーティング構築方法を記事でご紹介しました。

https://zenn.dev/harx/articles/d1835a5eb5d0e0

今回はその構成の中でよく登場する3つの基本的な画面遷移関数 go / push / replace の違いと使い分けについて、具体例を交えて解説していきます。

これらの関数は一見似ていますが、使用するシーンによって正しく使い分けることが重要です。
誤った使い方をすると、戻るボタンの挙動が不自然になるなど、ユーザー体験に影響を与える可能性があります。

本記事では、それぞれの関数の内部的な動作や使い分けの観点を整理しながら、実際にどのような場面で使うべきかを紹介していきます。

記事の対象者

  • Flutterで go_router を使って画面遷移を実装している方
  • go / push / replace の使い分けに自信が持てない、または誤った挙動に悩んだ経験がある方
  • 複数の画面(特に詳細画面など)をさまざまなルートから呼び出す設計をしている方
  • 戻るボタンの挙動や、ナビゲーションスタックの管理に関心がある方
  • go_router_builder を使って型安全なルート定義を行っているが、APIそのものの挙動を深く理解したい方
  • 実際のアプリ開発で、ユーザー体験を意識した遷移設計・画面構成を考えているFlutterエンジニア

記事を執筆時点での筆者の環境

[✓] Flutter (Channel stable, 3.29.0, on macOS
    15.3.1 24D70 darwin-arm64, locale ja-JP)
[✓] Android toolchain - develop for Android
    devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.2)
[✓] VS Code (version 1.100.0)

ソースコード

https://github.com/HaruhikoMotokawa/go_router_builder_sample/tree/feature/learn_screen_transition_functions

使用パッケージ

dependencies:
  cupertino_icons: 1.0.2
  derry: 1.5.0
  flutter:
    sdk: flutter
  flutter_hooks: 0.21.2
  go_router: 15.2.0
  hooks_riverpod: 2.6.1
  riverpod_annotation: 2.6.1
  very_good_analysis: 8.0.0

dev_dependencies:
  build_runner: 2.4.15
  flutter_lints: 5.0.0
  flutter_test:
    sdk: flutter
  go_router_builder: 3.0.0
  riverpod_generator: 2.6.5
  riverpod_lint: 2.6.5

3つの関数を概念で理解する

まずは大まかに概念を理解していきましょう。
go_router ではルーティングされた状態である画面から、別の画面に遷移するときに使用する関数が3つあります。
それが先ほどもあげた3つの関数です。

  • go
  • push
  • replace

今回のサンプルプロジェクトのルーティングは以下のように組まれています。

├── 🧭 NavigationShellRoute
│
├── 🏠 HomeBranch
│   └── /                      ← HomeRoute
│       └── /detail           ← DetailRoute
│
└── ⚙️ SettingsBranch
    └── /settings             ← SettingsRoute
        └── /settings/help    ← HelpRoute
コード
lib/core/router/route/route.dart
<AppShellRoute>(
  routes: [
    TypedStatefulShellRoute<NavigationShellRoute>(
      branches: [
        TypedStatefulShellBranch<HomeBranch>(
          routes: [
            TypedGoRoute<HomeRoute>(
              path: HomeRoute.path,
              name: HomeRoute.name,
              routes: [
                TypedGoRoute<DetailRoute>(
                  path: DetailRoute.path,
                  name: DetailRoute.name,
                ),
              ],
            ),
          ],
        ),
        TypedStatefulShellBranch<SettingsBranch>(
          routes: [
            TypedGoRoute<SettingsRoute>(
              path: SettingsRoute.path,
              name: SettingsRoute.name,
              routes: [
                TypedGoRoute<HelpRoute>(
                  path: HelpRoute.path,
                  name: HelpRoute.name,
                ),
              ],
            ),
          ],
        ),
      ],
    ),
  ],
)

上記を踏まえてそれぞれの関数を見ていきましょう。

go ➡️ 指定されたルートを構築する

go は指定されたルートをそのまま構築します。
言い換えれば現在のルートを破棄し、そのまま指定ルートを再構築します。

例えば現在は HomeRoute にいたとして、そこから DetailRoutego したとするとパスは /detail となります。

これだとそりゃそうでしょ、となるのですが、では逆に現在いる画面がホーム画面経由の詳細画面だった場合にHelpRoute.go を呼び出すとどうなるでしょうか?

正解は /settings/help になります。
/detail/help というように詳細画面の次にヘルプ画面を構築するわけではない、というのがミソです。
これが現在位置に関わらず、本来その画面の正規ルートを構築するという意味になります。

push ➡️ 現在のルートに指定画面を上乗せする

push は現在いる画面に指定の画面を積み上げるイメージです。
例えばホーム画面経由で詳細画面にいる状態でヘルプ画面にいきたい場合は push を使います。
するとパスは /detail/help となります。

replace ➡️ 指定の画面だけを再構築する

replace は指定した画面だけを再構築します。
例えばホーム画面経由の詳細画面にいたとします。パスは /detail となっています。
そこで詳細画面だけを再構築したいな、となった場合に使用すると /detaildetail の部分だけを再構築します。

gopush 基本の使い分け

ここではまず基本的な gopush の使い分けについてみていきたいと思います。
基本的には正規ルートであれば go を使い、そうでなければ push を使います。

今回、ホーム画面と設定画面の両方にヘルプ画面に遷移できるボタンを設置しました。
今回は実験ということでボタンをタップしたら gopush の遷移方法を選べるようにしています。

lib/presentation/shared/help_button.dart
return PopupMenuButton<String>(
  icon: const Icon(Icons.help_outline),
  onSelected: (value) {
    if (value == 'push') {
      const HelpRoute().push<void>(context);
    }
    if (value == 'go') {
      const HelpRoute().go(context);
    }
  },

  // ...
  
);

設定画面 ➡️ ヘルプ画面

設定画面からヘルプ画面へのルートは正規ルートです。
よってここでは go を使うのが正解なのですが、実は push でも全く同じ挙動を実現できます。

go で遷移した場合

push で遷移した場合

ホーム画面 ➡️ ヘルプ画面

ホーム画面からヘルプ画面へのルートは非正規ルートです。
よってここでは push を使うのが自然な形でしょう。

go でも動作はしますが、正規ルートである設定画面経由のヘルプ画面のルートを構築してしまいます。
つまり戻ると設定画面に戻ります。

ここで gopush のどちらを選択するかの判断は 戻るボタンを押した場合にどこに戻りたいのか の仕様によります。

push で遷移した場合

go で遷移した場合

詳細画面で比較する go / push / replace の使い分け

今回は詳細画面を例に考えてみます。

*詳細画面の基本仕様*
- 詳細画面はホーム画面経由で表示するのが正規ルートである
- 詳細画面は引数で指定されたIDのユーザー詳細情報を表示する
- 詳細画面の「次の人」ボタンをタップすると次のIDに該当するユーザー情報を表示する
- 詳細画面の「前の人」ボタンをタップすると前のIDに該当するユーザー情報を表示する

なお、ホーム画面から遷移する場合はユーザー一覧の中から選択したユーザーIDを引数で渡して遷移します。
また、設定画面から遷移する場合はユーザーIDは1を固定で渡します。

上記の 「前の人」ボタン「次の人」ボタン をタップした場合には詳細画面から同じ詳細画面に遷移する場合、go_router のどの関数を設定するのが良いのかを考えていきましょう。

replace

go_router の関数の中で最も使い所の理解に苦しむのが replace だと思われます。

もう一度 replace の挙動をおさらいしておくと、replace は指定した画面だけを再構築します。
それを踏まえた私が考える replace の使い所は以下です。

  • 別々のルートで呼ばれる可能性がある
  • 戻る画面で呼び出し元の画面に戻る場合

replace を使用した場合は前の人、次の人がそのまま構築されます。
ホーム画面経由であれ、設定画面経由であれ同じ挙動になり、画面左上の戻るボタン を押せばそれぞれホーム画面、設定画面に戻ります。

このようにどの画面から遷移してきても共通で動作するようにしたい場合は replace を検討したいところです。

ホーム画面 ➡️ 詳細画面 で replace

設定画面 ➡️ 詳細画面 で replace

push

push を使用した場合は前の人、次の人が今いる画面の上に積まれる形になります。
ホーム画面経由であれ、設定画面経由であれ同じ挙動になり、画面左上の戻るボタン を押せば上に積まれた画面が消えて前の画面に戻り、最終的にはそれぞれホーム画面、設定画面に戻ります。

この挙動は SNSのXの タイムライン➡️投稿の詳細➡️関連投稿の詳細 と同じ挙動になります。

replace と違って表示したユーザー情報を維持して前のユーザー情報に戻りたい場合は push を検討したいところです。

ホーム画面 ➡️ 詳細画面で push

※ 設定画面 ➡️ 詳細画面で push も同じなので動画は割愛。

go

結論から先に申し上げると今回の案件でいけばあまりおすすめはしない選択肢です。

実はホーム画面 ➡️ 詳細画面の場合だと go でも replace と同じ挙動になります。
要はルートとしては/detail/:id をまるまる作り直しているとはいえ、結果は同じです。

強いていうなら replace は ホーム画面の / はそのままにして後半の detail/:id だけを作り直しているので少し?パフォーマンスがいいくらいでしょうか。

しかし、これを設定画面 ➡️ 詳細画面で使用してしまうと戻る先はホーム画面になってしまいます。
仕様によってはいいのかもしれませんが、個人的には複数画面から呼ばれる画面であれば、go は控えるべきかと考えています。

設定画面 ➡️ 詳細画面 で go

まとめ

三つの関数の比較表

関数 主な挙動 戻る時の挙動 主な使用シーン
go 現在のルートを破棄して構築 呼び出し元に戻らない 正規ルート遷移
push 現在のスタックに積み上げ 呼び出し元に戻る 関連画面の一時表示など
replace 画面だけを置き換えて再構築 呼び出し元に戻る 同一ルート内の更新時

余談:イメージをつかもう

ここまで色々なパターンを見てきましたが、最後にイメージで掴んでみましよう。
ホーム画面経由で現在詳細画面にいるとして、そこからさらに詳細画面に遷移する場合に三つの関数の違いを絵にしたらこうなるかなと思っています。

  • 体がホーム画面
  • 顔が詳細画面

push ➡️ 既存のルートに詳細画面が上積みされるので、顔が追加される

go ➡️ 指定したルートを再構築するので、体も顔も新しくなる

replace ➡️ 指定した一部のルートだけを再構築するので、顔だけ新しくする

終わりに

本記事では、go_router を使う際によく出てくる基本的な遷移関数である gopushreplace の違いと使い分けについて、具体的なルート構成や挙動を交えて解説しました。

一見似たようなこれらの関数も、使用する場面や戻るボタンの挙動によって適切な選択肢が変わってきます。
実際のアプリ開発では、どの画面からどのように遷移してきたのか、またユーザーにどのような戻り挙動を提供したいかといった仕様面も含めて慎重に判断する必要があります。

特に replace に関しては使いどころがやや難しいですが、「同じルートを更新したい」「スタックを汚さずに再表示したい」といったケースでは非常に有効です。

今回の内容が、皆さんの go_router を使ったルーティング設計の参考になれば幸いです。

Discussion