SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Flutterで始めるモバイルアプリ開発

Flutter2の新しい画面遷移「Navigator 2.0」を使ったページ遷移を理解しよう

Flutterで始めるモバイルアプリ開発 第10回


パスを使った場合のNavigator2の利用方法

 続いて、パスによる画面遷移もサポートしたNavigator2での使い方を説明します。先ほど同じ画面遷移を行うサンプルを用意し、その動作イメージと主要要素の関係を示したのが図3です。

図3:パスを使う場合の動作イメージと主要要素の関係
図3:パスを使う場合の動作イメージと主要要素の関係

 前回に比べて大きく複雑になりました。全体を見ても今ひとつイメージがつかめないと思います。赤い部分として示した部分が前回と大きく異なる部分です。これらの違いについて、それぞれ説明します。

新しい要素の関係について

 今回、新しく追加しているクラスの関係についてのみに着目して、その役割を示したものが図4です。

図4:パスを使う場合の動作イメージと主要要素の関係
図4:パスを使う場合の動作イメージと主要要素の関係

 Routerが全体を管理します。RouterDelegateクラスは画面スタックとパスとの同期を管理しています。そして、RouterInformationParserでパス表現との同期を管理します。

 これらを使ったコード例がリスト3です。

[リスト3] MaterialApp.routerを使ったコード例(lib/main_2.dartの抜粋)
class _MainApp2 extends State{

  ItemRouterDelegate _routerDelegate = ItemRouterDelegate();
  ItemRouterInformationParser _routerInformationParser = ItemRouterInformationParser();

  @override
  Widget build(BuildContext context) {
    // (1) MaterialApp.routerを使ったインスタンスの作成
    return MaterialApp.router(
        title: "Nav2 App",
        debugShowCheckedModeBanner : false,
        // (2) パスとの同期を管理
        routeInformationParser: _routerInformationParser,
        // (3) 画面スタックを管理
        routerDelegate: _routerDelegate,
    );
  }
}

 先ほどのサンプルでは、MaterialAppを使っていましたが、今回は(1)Routerを作るために、MaterialApp.router()コンストラクタを使って作成します。そして、(2)routeInformationParserプロパティと(3)routerDelegateプロパティにそれぞれのインスタンスを設定します。

 また、今回はパスを管理するクラスが必要になります。このクラスはプログラム上でのパスの種類が管理できればどのような実装でも構いません。今回は、リスト4のように実装しました。

[リスト4] パスを管理するクラス(lib/AppRoutePath)
class AppRoutePath {
  final String id;
  AppRoutePath.list() : id = null;
  AppRoutePath.item(this.id);
  bool get isListPage => id == null;
  bool get isItemPage => id != null;
}

RouterDelegateクラス

 パスを使わないサンプルでは、NavigatorクラスをWidgetとして直接利用しました。しかし、パスを使う場合には直接Navigatorクラスを使わず、RouterDelegateクラスを継承したクラスを自作します。そのクラスが外部と同期をしつつNavigator内のpagesプロパティを操作します。リスト5はRouterDelegateクラスの実装例です。

[リスト5] RouterDelegateクラスの実装例(lib/ItemRouterDelegate.dartの抜粋)
// (1) RouterDelegateの実装クラスを作成する
class ItemRouterDelegate extends RouterDelegate
    with ChangeNotifier, PopNavigatorRouterDelegateMixin {
  final GlobalKey navigatorKey;

  ProductItem _selectedItem;

  List items = [
    ProductItem("id1", "商品A"),
    ProductItem("id2", "商品B")
  ];

  ItemRouterDelegate() : navigatorKey = GlobalKey();

  //  (2) 表示するアイテムが選択された時の処理
  void _onTapItem(ProductItem item) {
    _selectedItem = item;
    //	(3) 変更通知をする
    notifyListeners();
  }

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: [
        MaterialPage(
            key: ValueKey('ProductListPage'),
            child: ProductListPage(items, _onTapItem)),
        if (_selectedItem != null)
          MaterialPage(child: ProductItemPage(_selectedItem))
      ],
      onPopPage: (route, result) {
        print('on pop page');
        if (!route.didPop(result)) {
          return false;
        }
        _selectedItem = null;
        //	(4) 変更通知をする
        notifyListeners();
        return true;
      },
    );
  }

  //  (5) 新しいパスが設定された時
  @override
  Future setNewRoutePath(AppRoutePath path) async {
    if (path.isItemPage) {
      //  パスに従った詳細ページ用のデータを設定する
      _selectedItem = items.firstWhere((element) => element.id == path.id);
    }
    return;
  }
  //	(6) 現在の状態をパスで表現した場合
  AppRoutePath get currentConfiguration => _selectedItem == null
      ? AppRoutePath.list()
      : AppRoutePath.item(_selectedItem.id);
}

 ItemRouterDelegateクラスは、StatefulWidgetクラスの実装ではありません。そのため、(2)のように、変更を通知するためにChangeNotifierクラスと、pop処理を行うためにPopNavigatorRouterDelegateMixinクラスをミックスインとして利用します。

 そして、リストのアイテム部分をタップされた時の処理を(2)のように定義します。今回はsetStateの代わりに(3)のようにnotifyListenersメソッドを使って変更されたことを通知します。

 この変更は、画面更新だけではなく、RouteInformationParserにも通知されます。同様に(4)でもnotifyListenersメソッドをコールします。ここまでの処理で画面更新は完了です。

 そして、以降のコードがパスと同期をするためのコードになります。(5)はパスが変更された時に、画面を同期するための処理です。パス上にProductItemを識別するidを抽出して、該当のインスタンスを設定しています。(6)は現在の状態をパスで表現した場合の処理です。(5)と(6)を通じてAppRoutePathとの同期を行います。

RouterInformationParserクラス

 RouterInformationParserクラスは、パス(文字列表現)との同期を行うためのクラスで、リスト6がサンプルの実装コードです。

[リスト6] RouterDelegateクラスの実装例(lib/ItemRouterInformationParser.dartの抜粋)
class ItemRouterInformationParser extends RouteInformationParser {
  // (1) パスが変更された時の処理
  @override
  Future parseRouteInformation(
      RouteInformation routeInformation) async {

    if (routeInformation.location == '/') {
      return AppRoutePath.list();
    } else {
      // (2) /id1 のような入力になるので、最初の"/"をとる
      var id = routeInformation.location.substring(1);
      return AppRoutePath.item(id);
    }
  }

  //	(3) 状態が変わった時に、パスを反映する処理
  @override
  RouteInformation restoreRouteInformation(AppRoutePath path) {
    if (path.id != null) {
      //	(4) アイテムが設定されている場合のURLを設定する
      return RouteInformation(location: '/' + path.id);
    }
    return RouteInformation(location: '/');
  }
}

 パスが外部(ブラウザの場合であればURLを直接指定など)から変更された場合には、(1)parseRouteInformationメソッドがコールされるので、アプリケーション内のAppRoutePathインスタンスを作成し返します。詳細ページの場合には、(2)のように入力された文字列から判断します。一方、内部の状態が変わった場合には、(3)restoreRouteInformationメソッドがコールされるので、AppRoutePathインスタンスからパスの文字列を作成します。

まとめ

 Navigator2を使うか、Navigator1を使うかを迷われた場合に、1つの選択肢として、パス表現を使うかどうかが大きな判断ポイントになると思います。今回のサンプルのようにシンプルな構造であれば、Navigator2を使わなくても問題ないはずです。

 一方、複雑なアプリケーションになると、自分が作った画面をすぐに確認するためにパス表現が使えるのは便利です。また、広告関連であると外部から直接アプリ内の特定画面に直接アクセスすることも多々あるので、そのようなケースでは便利になるはずです。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
Flutterで始めるモバイルアプリ開発連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 小林 昌弘(コバヤシ マサヒロ)

WINGSプロジェクト について> 有限会社 WINGSプロジェクト が運営する、テクニカル執筆コミュニティ(代表 ...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/14532 2022/09/05 20:14

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー