[読書会]ナビゲータとルーティング

目次 index【閲覧時間3分】 click !!>>

1.概要

 ナビゲーションルーティングに関する基本的な考え方について触れています。

1-1.Navigator Router

 アプリ内での画面遷移深いリンク(deep link)を扱うための包括的なシステムを提供しており、
主に2つのコンポーネント( Navigator Router )が使われます。

(1)Navigator

 Navigatorは、アプリでの簡単な画面遷移に使われる主要なウィジェットです。
Navigatorは、ウィジェットのスタックを管理し、pushやpopメソッドを使って、画面を積上したり戻ったりします。
(これは、アプリが複雑なナビゲーション構造や深いリンクを必要としない場合に十分です。)

(2)Router

 Routerは、特に深いリンク(deep linking)の様な高度なナビゲーション要件があるアプリで使用されます。
(例えば、ユーザが特定URLにアクセス時、アプリがそのURLに対応する特定画面表示が求められる場合等)
Routerは(Navigatorをベースにしつつ)ウェブブラウザのアドレスバーのURLアプリ内の画面状態同期させる為にも使用されます。これにより、AndroidiOSだけでなく、ウェブアプリでのナビゲーションにも対応しています

1-2.Deep Linking

 ディープリンクとは、アプリの特定画面やコンテンツ直接アクセスする為のリンクです。
(例えば、URLや通知をタップして、アプリ内の特定ページ直接遷移させる仕組み
AndroidやiOSアプリでは、これらのリンクを適切に処理するために設定が必要です。

 ディープリンクをサポートする為に、アプリ内での画面遷移Routerを使って正確に処理し、
またウェブ上ではアドレスバーとアプリの状態を同期させることで、URLに応じた画面表示を実現します。

 これらのナビゲーションシステムは、小さなアプリでも大規模なアプリケーションでも使用できますが、Deep linking が含まれる場合は、より高度なRouterの使用が推奨されます

1-3.補足

 ディープリンクの設定に関しては、Flutterの公式ドキュメント「Deep Linking」を参照すると、AndroidやiOSでの設定手順が詳細に説明されています。

2.Navigator の利用

 Navigatorウィジェットの使用方法について解説です。

2-1.Navigatorとは

 Navigatorウィジェットは、アプリ内での画面遷移を管理するための機能です。プラットフォームごとに適切な遷移アニメーションを使用し、画面(スクリーン)をスタックとして表示します。これは、アプリ内でのページ遷移が「スタック」によって管理されることを意味します。
 ユーザが新しいページに移動すると、そのページはスタックの上に積み重ねられ、前のページはその下に残ります。
ユーザが「戻る」操作をすると、そのスタックから一番上のページが「ポップ」されて、前のページが表示される仕組みです。

2-2.Navigatorの使い方

 新しい画面に遷移するには、Navigator.of(context).push()メソッドを使用します。
このメソッドは、BuildContext を通じて Navigator にアクセスし、新しいRoute(画面の遷移)をスタックに追加します

Dart
onPressed: () {
  //Navigator.of(context):
  //現在のBuildContextに関連するNavigatorを取得します。
  //push():
  //新しい画面をスタックに追加するためのメソッド。
  //ここでは、新しいページとしてSongScreenが表示されます。
  Navigator.of(context).push(
    //MaterialPageRoute:
    //画面遷移時のアニメーション効果やその他の画面遷移
    //の挙動を定義するためのRouteクラスです。
    //Material Designに基づくアニメーションが使用されます。
    //例えば、MaterialPageRouteを使うと、Androidなら「右から左へ」
    //のスライドアニメーションが、iOSでは「下から上へ」
    //のモーダルアニメーションが適用されます。
    MaterialPageRoute(
      builder: (context) => const SongScreen(
        song: song
      ),
    ),
  );
},
child: Text(song.name),

2-3.Routeについて

 Navigatorが管理しているのは、Routeオブジェクトのスタックです。
push()メソッドは、このスタックの上に新しいRouteオブジェクト(つまり新しい画面)を追加します。
(例えば、MaterialPageRouteは、Material Designに沿った遷移アニメーションを指定するためのサブクラスです。
他にも、CupertinoPageRouteなど、プラットフォーム固有のスタイルに対応するクラスもあります。)

2-4.補足

 Flutter Cookbookには、Navigatorを使用して画面遷移を管理するための具体的なレシピがいくつか紹介されています。
また、Navigator APIの公式ドキュメントも参照することで、より多くの例や詳細な情報を得ることができます。

3.名前付き routes の利用

 MaterialApp の routes パラメータNavigator を使用した、簡単なナビゲーションやディープリンクの設定についてです。

3-1.Named Routes(名前付きルート)

 複数の画面(ウィジェット)間のナビゲーションを扱うために「名前付きルート(Named Routes)」という機能があります。
名前付きルートは、画面を一意に識別するための文字列を使用し、ルート間簡単に移動できる様にします。
これにより、特定ルートに関連するウィジェットを簡単に取得できます。

3-2.routesパラメータの使用例

 MaterialApp の routes パラメータルート(画面)を指定 し、簡単にルーティング(ナビゲーション)を設定できます。
routes は、ルート名それに対応するウィジェットマップとして指定します。

Dart
@override
Widget build(BuildContext context) {
  return MaterialApp(
    routes: {
      //ホーム画面のルート
      //(/ はアプリケーションのホーム画面(HomeScreen)に対応しています。)
      '/': (context) => HomeScreen(),
      //詳細画面のルート
      //(/details は詳細画面(DetailScreen)に対応しています。)
      '/details': (context) => DetailScreen(),
    },
  );
}

 これにより、Navigator.pushNamed()を使用して、指定されたルート移動できます。
(つまり、Navigator.pushNamed( context, ‘/details’ ) で DetailScreen に遷移します。)

注意:
 殆どの用途で名前付きルートを使うことはお勧めしません。
 詳細については、以下の制限事項のセクションを参照して下さい。

3-3.DeepLink と Routes

 DeepLink は、アプリの特定画面に直接移動するリンクです。
routes パラメータを使って定義された名前付きルートは、ディープリンクとしても使用でき、URLやリンクから直接アプリの特定の部分にアクセスできます。
(例えば、/detailsというURLでアプリ内の詳細ページにアクセスすることが可能です。)

3-4.まとめ

 MaterialApp.routesとNavigatorを組み合わせることで、シンプルなナビゲーション機能とディープリンクを簡単に実装することができます。より複雑なナビゲーションが必要な場合は、NavigatorやRouterを組み合わせて、柔軟にナビゲーションを実装することが推奨されます。

 詳細な例としては、Flutter Cookbookの「Navigate with named routes」レシピを参照してください。

3-2.制限事項

 名前付きルート(Named Routes)は、以下の制限があり、全てのアプリでの使用が推奨されている訳ではありません。
このような制限により、名前付きルート非常にシンプルなナビゲーションを持つアプリケーションでは便利ですが、ディープリンクブラウザとの連携が重要なアプリには適していません
その為、多くの場合、より柔軟なルーティング提供するRouterを使用したナビゲーションの実装が推奨されます。

(1)ディープリンクのカスタマイズ不可

 名前付きルートはディープリンクの処理をサポートしていますが、その挙動はカスタマイズできません
ディープリンクを受け取った場合、現在の画面に関係なく、Navigatorのスタックに新しいルートが追加されます。
(例えば、ユーザーが既に特定ページにいる場合でも、新たに同ページをスタックに積上します。
この様に、ページの重複表示を防ぐ様な制御が難しい
点が課題です。)

(2)ブラウザの「戻る」や「進む」ボタンのサポート不足

 名前付きルートを使用した場合、アプリはウェブ上でブラウザの「進む」ボタンのサポートがありません
ウェブアプリでは、ユーザーがブラウザの「戻る」ボタンや「進む」ボタンを押した際に、その挙動が通常のウェブナビゲーションのように機能する必要がありますが、名前付きルートではこれがサポートされていません。
結果として、ブラウザ上でのユーザー体験が損なわれる可能性があります。

4.Router の利用

 Routerは、複雑なナビゲーション高度なルーティング要件を持つアプリで使用される仕組みです。
(特にウェブアプリの様に、ユーザが特定画面に直接リンクできる場合や、複数のNavigatorウィジェットを必要とするアプリに適しています。)

4-1.Routerの使用方法と利点

 Router go_router の組み合わせを使用すると、複雑なナビゲーションディープリンクをサポートするアプリで非常に効果的です。特にウェブアプリや、異なる部分で異なるナビゲーションを管理する必要がある場合に有用です。

(1)直接リンクに対応

 Router は、ウェブアプリでユーザがURLを使って特定のページに直接リンクした場合、そのURLパスを解析して適切な画面を表示する機能を提供します。
(これにより、例えばブラウザのアドレスバーに特定のURLを入力した際に、その画面が正しく表示されるようになります。)Navigator 画面遷移スタック形式 で管理しますが、Router URLパス に基づいて動的に画面構成します。

(2)複数のNavigatorサポート

 一部のアプリでは、異なる画面で別々のNavigatorを持つことが必要になります。
Router は、これらの複数の Navigator を適切に制御し、異なる部分でのナビゲーションを管理します。

(3)go_router パッケージの利用

 Router を使用する際には、一般的に go_router のようなパッケージが使用されます
このパッケージはルーティングを簡単に設定でき、URLパス解析し、Navigator の動作を制御します。
(例えば、MaterialApp.routerやCupertinoApp.routerを使用する際に、go_routerの設定を提供することで、アプリのルーティングが効率的に行えます。)

MaterialApp.router と go_routerの設定を行い、効率的にルーティングを行う(例):
MaterialApp.router(
  routerConfig: GoRouter(
    //ルーティング設定がここに記述される
  )
);

4-2.ディープリンク対応のメリット

 ディープリンクを受け取ると、アプリケーションはURLに基づいて常に適切な画面を表示するため、ナビゲーションがより安定し、予測可能になります。go_router などのパッケージは宣言的なアプローチでルーティングを設定するため、ルートの状態管理が簡単になります。

4-3.より高度なアプリ開発について

 より高度なFlutterアプリ開発に関する内容です。
通常のナビゲーションパッケージ(例: go_router 等)を使用する代わりに)アプリ開発者が完全にカスタマイズ可能なナビゲーションとルーティングシステムを作成したい場合についての話題です。

 以下では、RouteInformationParser RouterDelegate の利用について触れています。

(1)RouteInformationParser

 RouteInformationParser は、ブラウザのURLプラットフォーム の ディープリンクから情報を解析し、アプリの状態に変換する役割を持ちます。URLパス ディープリンク からアプリの内部状態を決定し、その情報に基づいてどの画面を表示するか制御します

(2)RouterDelegate

 一方、RouterDelegateは、ナビゲーション実際にどのように処理するか定義するクラスです。
このクラスをオーバーライドすることで、アプリの状態に応じページのスタックを細かく管理できます

(3)Navigator.pages

 (通常の Navigator では画面遷移のスタックは暗黙的に管理されますが)Navigator.pages を使用すると、開発者が明示的画面のスタックを制御できます。(Page オブジェクトリスト提供することで、現在のアプリの状態に応じてどの画面を表示するか柔軟に設定できます。)

(例えば、特定のイベントが発生したときに、どのページがナビゲーションスタックに追加されるか、または削除されるかを詳細に制御することが可能です。これにより、ブラウザの前進・後退ボタンに対応した高度なナビゲーションが実装できるようになります。)

(4)まとめ

 (通常の go_router 等のルーティングパッケージは、シンプルで宣言的なアプローチでディープリンクやナビゲーションを管理できますが)もしアプリのナビゲーションページ遷移細かく制御したい場合は、RouteInformationParser RouterDelegate オーバーライドして独自のナビゲーションシステムを実装するのが推奨されます。

 Navigator.pages を使うことで、ページのスタックを明示的に管理でき、複雑なナビゲーションの要件にも対応できます

 このアプローチは、より細かいナビゲーションの制御が必要な大規模なアプリやカスタム要件の多いアプリに適しています。

5.Router と Navigator の同時利用

 Router Navigator どのように協力して機能するかについての話題です。

5-1.Router と Navigator の連携

 Router Navigator は、各々で異なる役割を持ちながら、連携してアプリの画面遷移を制御します。

(1)Router を使ったナビゲーション

 Routerを使ったナビゲーションでは、宣言的なルーティングパッケージ(たとえば go_router 等)を利用して画面遷移を行います。この方法では、Navigator ページのスタックが保持されます。ページ Navigator pages 引数を使って定義されるため、各ページが「ページベース」( page-backed )で作成されます。
(このページベースのルートは、ディープリンクをサポートします。
つまり、ユーザが特定のURLにアクセスすると、直接そのページに遷移することが可能です。)

(2)Navigator によるナビゲーション

 一方で、Navigator.push() showDialog() の様な直接的な方法でルートを追加した場合、それらのルートは「ページレス」( pageless )になります。これは、ディープリンクとして対応しないため、ユーザが URL を指定して直接そのルートにアクセスすることはできません。

5-2.ページベースとページレスの違い

  • ページベースのルート:
     宣言的なパッケージ go_router 等)で生成され、ディープリンクが可能です。
  • ページレスのルート:
     Navigator.pushやshowDialog 等のメソッドで生成され、ディープリンクに非対応です。

5-3.ページベースのルートが削除された場合の挙動

 特に、ページベースのルートが削除されると、その後に追加されたページレスのルートも一緒に削除されます
これは、ナビゲーションスタック内でページベースのルートが境界線のように機能し、ページレスのルートがそれに付随して存在しているためです。この仕組みは、ディープリンクによるナビゲーション時に特に重要です。ディープリンクによって特定のページベースのルートが削除されると、その後のページレスのルートも一緒に削除される為、意図しないナビゲーションの挙動を防ぐことができます。

5-4.まとめ

 Router Navigator は、異なる方法でナビゲーションを制御することができますが、連携して動作することで、より柔軟な画面遷移を実現します。
 ディープリンクに対応するためには、Router やページベースのルートを使うことが推奨されますが、特定のケースでは Navigator.push のようなページレスのルートも有効です。

5-5.注意( WillPopScope 制限 について )

 WillPopScope は、ユーザが「戻る」操作を行ったときに、そのナビゲーションを防ぐ為に使用されます。
しかし、これはページベースのナビゲーション(page-backed routes)では機能しません。
代わりに、ページベースのルートナビゲーションを制御する為には、使用しているルーティングパッケージのAPIを確認する必要があります。

(1)ページベースのルートについて

 ページベースのルートは、通常の Navigator.push() pop() メソッド によるナビゲーションではなく、Router go_router などの宣言的なルーティングパッケージを使用して管理されるため、WillPopScope ではそれらのルートのナビゲーションを防ぐことができません
このため、WillPopScope Navigator を直接使っている場合に有効ですが、より高度なナビゲーションを行っている場合には、そのルーティングパッケージ固有の方法でナビゲーションを制御する必要があります。

(2)解決策

 ルーティングパッケージのAPIドキュメントを参照し、特定のページでの「戻る」操作を制御する方法を調べる必要があります
(例えば、go_router 等のパッケージでは、ナビゲーションを制限する為の特定のメソッドや設定が提供されています。)

6.ウェブサポート

 ここでは、Router クラスを使用したアプリが、Webブラウザの履歴操作にどう対応するかについて触れています。

6-1.ブラウザの履歴APIとの連携

 Routerクラスを使用すると、アプリはブラウザの History API連携し、ブラウザの「戻る」「進む」ボタンを使ったナビゲーションが一貫した動作になるように対応します。具体的には、Router を使って新しいページにナビゲーションするたびに、ブラウザの履歴スタックにエントリが追加されます。

6-2.「戻る」ボタンの動作

 ユーザがブラウザの「戻る」ボタンを押した場合、直前に訪れたページ(Routerを使って表示されたページ)が表示されます。これは、ブラウザの履歴スタックでページが逆順(逆時系列)に遡って表示される為です。例えば、ユーザがアプリ内でページを Navigator から「ポップ」し、その後ブラウザの「戻る」ボタンを押すと、以前のページが履歴スタックに戻されて再び表示されます。

6-3.実用例

 これにより、ユーザはアプリ内で Routerを 介して行ったナビゲーションの履歴シームレスに利用でき、ネイティブなブラウザ操作との統一感が得られます。
(例えば、アプリ内でページ移動を行い、その後ブラウザの「戻る」ボタンを押しても、スムーズに前のページに戻ることが可能です。)

 この仕組みは、Web上での一貫したナビゲーション体験を提供するために非常に重要です。

7.詳細情報

 ナビゲーションやルーティングに関するさらなる情報源を紹介しています。

(1)Flutter Cookbook

 Flutter Cookbookには、Navigatorを使ったナビゲーションのさまざまなレシピが含まれています。これを利用すると、実際のアプリケーションでナビゲーションの基本的なパターンを学ぶことができます。

(2)Navigator と Router の API ドキュメント

 これらのAPIドキュメントには、ルーティングパッケージを使わずに宣言的なナビゲーションをセットアップする方法の詳細が記載されています。FlutterのNavigatorとRouterの構造や使い方について深く理解したい場合に役立ちます。

(3)Material Design ドキュメント:ナビゲーションの理解

 Material Designのドキュメントには、アプリのナビゲーションをデザインするための基本的な概念が記載されています。ここでは、前進ナビゲーション、上向きナビゲーション、時系列ナビゲーションなどの概念が詳しく説明されています。

(4)Mediumの記事:Flutterの新しいナビゲーションとルーティングシステム

 Mediumに掲載された記事「Learning Flutter’s new navigation and routing system」では、ルーティングパッケージを使用せずに、Routerウィジェットを直接使用する方法が説明されています。

(5)Routerの設計ドキュメント

 Router APIの設計の動機や詳細については、Routerの設計ドキュメントを参照できます。ここには、Router APIの背後にある設計理念や、それがどのように開発されたかが詳しく記載されています。

 これらのリソースを通じて、Flutterのナビゲーションシステムについてより深く理解することができます。

コメントを残す