🛣️

go-router でできること

2024/04/04に公開

私は誰?

加藤 竣と申します。
Androidアプリ開発者だったのですが、色々あって大学院にいます。
最近&AI(旧ドリグロ)でインターンを始めることになったのでFlutterを勉強中です。
今回の記事はインターン開始前に、週1でやっているFlutter勉強会の発表内容をまとめたものです。
勉強会っていいですね。osh

テーマ「goRouterでできること」

皆さんgoRouterご存知ですか?

めちゃくちゃ便利なのでぜひ使ってみてください。

goRouterとは?

navigator2.0に対応する画面遷移を簡単に実装できるパッケージです。

割と簡単に、『よくある画面遷移処理』を実装できます。

<そもそもnavigator2.0とは>
従来のnavigator1.0が持ついくつかの課題を解消するために偉い人が考えた新たな画面遷移の仕組み

従来の課題はたくさんあるが例えばこんなの

  1. 画面同士の関係性を入れ子構造で管理できない
  2. アプリの状態を監視して再構築できない
  3. Webアプリでもurlを正しく表示させたい
  4. androidの戻るボタンの処理を正しく動かしたい

どれか一つでも当てはまるならnavigator2.0対応させるのが吉

基本の画面遷移

基本のキです。

さらっとご紹介

ルーティング定義

final goRouter = GoRouter(
  // アプリが起動した時の画面を指定
  initialLocation: '/',
  // ルート定義部分
  routes: [
    GoRoute(
      path: '/',
      name: 'initial',
      builder: (BuildContext context,GoRouterState state) {
        return MaterialPage(
          key: state.pageKey,
          child: const MyHomePage(),
        );
      },
    ),
    // ex) 適当な画面
    GoRoute(
      path: '/hoge',
      
      name: 'hogeページ',
      pageBuilder: (context, state) {
        return MaterialPage(
          key: state.pageKey,
          child: const HogeScreen(),
        );
      },
    ),
~~~~~~~~~

呼び出し部分

// 戻るボタンを表示させる場合
context.push('/hoge')

// 戻るボタンを表示させない場合
context.go('/hoge')

// 前の画面に戻る場合
context.pop()

引数を渡す

いろんなやり方がありますが、今回はurlパラメーターみたいに渡す方法

      GoRoute(
        path: '/detail/:user_name/:user_id',
        builder: (BuildContext context, GoRouterState state) {
          final userName = state.params['user_name'];
          final userId = state.params['user_id'];

          return UserDetailScreen(
            userName: userName!,
            userId: int.parse(userId!),
          );
        },
      ),

認証①

リアルタイムに監視する必要がないなら簡単です。

routing定義部分に条件分岐を含んだリダイレクト処理を挟めば、勝手にやってくれます。

redirect: (context, state) {
      // ログインしていない場合のリダイレクト処理
      if (authState.value == null) {
        // すでにログイン画面にいるなら何もしない
        // state.matchedLocationで現在のurlが取得できる
        if (state.matchedLocation == '/' || state.matchedLocation == '/sign-up') {
          return null;
        } else {
          // ログインしていないのにログイン画面以外にいる場合は、ログイン画面に強制的にリダイレクトさせる
          return '/';
        }
      } else {
        // ログインしている場合のリダイレクト処理
        // ログインしているのにログイン画面(新規登録画面含む)にいる場合は、アプリのメイン画面にリダイレクトさせる
        if (state.matchedLocation == '/') {
          return '/app/my-page';
        }
      }

認証ガード②

今回のメインの一つです。

ログイン状態をリアルタイムに監視する必要があるなら少し工夫が必要でした。

上記の認証ガード①に加えて

認証状態をProviderで監視して、値の変更があったらリダイレクト処理を呼び出す必要があります。

追加するものはrefreshListenable: authStateNotifier

providerの生成例(ここではstreamProviderを使います)

class _AuthStateNotifier extends ValueNotifier<User?> {
  _AuthStateNotifier() : super(null);
  void change(User? v) => value = v;
}

final authStateNotifier = _AuthStateNotifier();

final authProvider = StreamProvider<User?>(
      (ref) {
    ref.listenSelf((_, v) => authStateNotifier.change(v.value));
    return FirebaseAuth.instance.authStateChanges();
  },
);

goRouterの監視部分


refreshListenable: authStateNotifier,

タブでの画面遷移

shellRouteというものを使います。

// アプリのメイン画面(タイムライン、本棚、マイページ)はタブ遷移のためShellRouteを使う
      ShellRoute(
        builder: (context, state, child) {
          int currentIndex = 0;
          if (state.matchedLocation == '/app/timeline') {
            currentIndex = 0;
          } else if (state.matchedLocation == '/app/my-library') {
            currentIndex = 1;
          } else if (state.matchedLocation == '/app/my-page') {
            currentIndex = 2;
          }

          return Scaffold(
            body: child,
            bottomNavigationBar: BottomNavigationBar(
              currentIndex: currentIndex,
              onTap: (index) {
                switch (index) {
                  case 0:
                    context.go('/app/timeline');
                    break;
                  case 1:
                    context.go('/app/my-library');
                    break;
                  case 2:
                    context.go('/app/my-page');
                    break;
                }
              },
              items: const [
                BottomNavigationBarItem(icon: Icon(Icons.timeline), label: 'タイムライン'),
                BottomNavigationBarItem(icon: Icon(Icons.library_books), label: '本棚'),
                BottomNavigationBarItem(icon: Icon(Icons.person), label: 'マイページ'),
              ],
            ),
          );
        },
        routes: [
          GoRoute(
            path: '/app/my-library',
            name: "my-books",
            pageBuilder: (context, state) => MaterialPage(
              key: state.pageKey,
              child: MyLibraryPage(),
            ),
          ),
          GoRoute(
            path: '/app/timeline',
            name: "timeline",
            pageBuilder: (context, state) => MaterialPage(
              key: state.pageKey,
              child: TimelinePage(),
            ),
          ),
          GoRoute(
            path: '/app/my-page',
            name: "my-page",
            pageBuilder: (context, state) => MaterialPage(
              key: state.pageKey,
              child: const MyPage(),
            ),
          ),
        ],
      ),

アニメーションでの画面遷移

発表までに調べ切れませんでしたが、pageBuilderを使えばできるそうです。

まとめ

goRouterはnavigator2.0を実装したい時にめちゃくちゃ便利なライブラリ

普通の画面遷移だけでなく以下の遷移を実装できる

  1. 引数わたし
  2. 認証ガード
  3. タブ遷移
  4. アニメーション遷移

最後に

ちなみにnavigator2.0に対応させたいならgo_router以外のパッケージの選択肢もあります。(参考)

ただ、本家Flutterチームに横取りされてメンテナンスされている点や現状のライブラリのいいね数等を鑑みて、初心者はgoRouter選んでおけば問題ないかと思います。

また、そもそもnavigator1.0のままでも問題ない場合もあるので、使うかどうかはプロジェクトの規模や目的によると思います。

Discussion