0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GoRouter state.pageKey, state.pageKey.value?

Posted at

state.pageKeyの内部実装を見てみる

UdemyでGoRouterの講座をやってみたのだが、間違っている箇所があった😅
そもそもルートがネストしてないとエラー出るのだが。。。
動画が少し古いのだろう。バージョンアップするとパスの設定は変わるんですよね。

リファクタリングしたので書いてあるソースコードでどんな役割なのかわからない以下のコードをこのあと調べる。

  • state.pageKey
  • state.pageKey.value

Githubのソースコード

リファクタリングしたのでエラーは解消できた。

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:udemy_go_router/detail_page.dart';
import 'package:udemy_go_router/home_page.dart';
import 'package:udemy_go_router/login_page.dart';
import 'package:udemy_go_router/sessions.dart';
import 'package:udemy_go_router/splash_page.dart';

/// The route configuration.
final GoRouter router = GoRouter(
  routes: <RouteBase>[
    /// TOP LEVEL PATH
    GoRoute(
      path: '/',
      name: 'splash',
      builder: (BuildContext context, GoRouterState state) => const SplashPage(),
    ),
    /// TOP LEVEL PATH
    GoRoute(
        path: '/login/redirection',
        name: 'login-redirection',
        redirect: (BuildContext context, GoRouterState state) async {
          if (await checkedLoggedIn()) {
            return '/home';
          } else {
            return '/login';
          }
        }),
    /// Page
    GoRoute(
        path: '/home',
        name: 'home',
      pageBuilder: (context, state) => NoTransitionPage<void>(
        key: state.pageKey,
        restorationId: state.pageKey.value,
        child: const HomePage(),
      ),
      routes: [
        GoRoute(
          path: 'detail/:id',  // /home/detail/:id となる
          name: 'detail',
          builder: (context, state) => DetailPage(
            id: state.pathParameters['id']!,
          ),
        ),
      ],
    ),
    /// Login
    GoRoute(
        path: '/login',
        name: 'login',
        pageBuilder: (context, state) => NoTransitionPage<void>(
          key: state.pageKey,
          restorationId: state.pageKey.value,
          child: const LoginPage(),
        ),
      // builder: (context, state) => const LoginPage(),
    ),
  ],
);

Name Route

モバイルアプリで画面遷移するときは、context.goNamedにしないとエラーが発生する。Flutter Webだと公式は名前つきルートを使うのを推奨していないそうだが、意外と現場ではやってるのを見かける。Webでやるときは、context.goにすれば良いと思う。

ネストしたルートだけの気もするが。。。

パスパラメーターを使用する必要があったのでコードも修正した。

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:udemy_go_router/sessions.dart';

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ホーム'),
        actions: [
          TextButton(onPressed: () async {
            await logout();
            if(context.mounted) {
              context.go('/login');
            }
          }, child: const Text('ログアウト'))
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(onPressed: () {
              // ✅ 正しい使用方法
              context.pushNamed(
                  'detail',
                  pathParameters: {'id': 'A'}
              );
            }, child: const Text('Aを閲覧する')),
            ElevatedButton(onPressed: () {
              // ✅ 正しい使用方法
              context.pushNamed(
                  'detail',
                  pathParameters: {'id': 'B'}
              );
            }, child: const Text('Bを閲覧する')),
            ElevatedButton(onPressed: () {
              // ✅ 正しい使用方法
              context.pushNamed(
                  'detail',
                  pathParameters: {'id': 'C'}
              );
            }, child: const Text('Cを閲覧する')),
          ],
        ),
      ),
    );
  }
}

state.pageKey, state.pageKey.value

内部実装によるとこのようなことが書いてあった。

What state.pageKey?

/// このサブルートのユニークな文字列キー。
/// 例えば
/// dart /// ValueKey('/family/:fid') ///

サブルートにユニークな文字列キーが渡されるようだ。

スクリーンショット 2024-12-02 9.39.36.png

/// See also:
///
///  * [Widget.key], which discusses how widgets use keys.
class ValueKey<T> extends LocalKey {
  /// Creates a key that delegates its [operator==] to the given value.
  const ValueKey(this.value);

  /// The value to which this key delegates its [operator==]
  final T value;

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType) {
      return false;
    }
    return other is ValueKey<T>
        && other.value == value;
  }

  @override
  int get hashCode => Object.hash(runtimeType, value);

翻訳したコード

/// こちらもご覧ください:
///
ウィジェットがキーを使用する方法については、 /// * [Widget.key] も参照してください。
class ValueKey<T> extends LocalKey { /// 与えられた値に [operator==] を委譲するキーを作成します。
  /// 与えられた値に [operator==] を委譲するキーを作成します。
  const ValueKey(this.value)

  /// このキーが [operator==] を委譲する値。
  final T value

  オーバーライド
  bool operator ==(Object other) { 次のようになります。
    if (other.runtimeType != runtimeType) { if (other.runtimeType != runtimeType)
      return false
    }
    return other is ValueKey<T>
        && other.value == value
  }

  オーバーライド
  int get hashCode => Object.hash(runtimeType, value)

委譲するキーなるものの値なようだ。

なぜstate.pageKey, state.pageKey.valueが必要なのかというと、pageBuilderを使用し、NoTransitionPageを使用するからである。

/// トランジションなしのカスタムトランジションページ。
class NoTransitionPage<T> extends CustomTransitionPage<T> {
  /// 遷移機能を持たないページのコンストラクタです。
  /// トランジション機能を持たないページのコンストラクタです。
  const NoTransitionPage({
    required super.child,
    super.name,
    super.arguments,
    super.restorationId,
    super.key,
  }) : super(
          transitionsBuilder: _transitionsBuilder,
          transitionDuration: Duration.zero,
          reverseTransitionDuration: Duration.zero,
        );

  static Widget _transitionsBuilder(
          BuildContext context,
          Animation<double> animation,
          Animation<double> secondaryAnimation,
          Widget child) =>
      child;
}

NoTransitionPageを使用する主な理由について説明します:

  1. アニメーションの制御
  • NoTransitionPageは、ページ遷移時のアニメーションを無効化します
  • デフォルトのMaterialPageTransitionsBuilderによる遷移アニメーションを避けたい場合に使用します
  • 特に以下のような場合に有効です:
    • スプラッシュ画面からホーム画面への遷移
    • ログイン画面からメイン画面への遷移
    • 即時的な画面切り替えが必要な場合
  1. パフォーマンスの最適化
pageBuilder: (context, state) => NoTransitionPage<void>(
  key: state.pageKey,
  restorationId: state.pageKey.value,
  child: const HomePage(),
)
  • アニメーションを省略することで、遷移時のパフォーマンスが向上します
  • 特に複雑なUIや大量のデータを扱う画面での遷移時に効果的です
  1. ユーザー体験の考慮
  • 特定のケースでは、アニメーションなしの即時遷移の方が適切な場合があります:
    • 認証フロー
    • エラー画面への遷移
    • モーダル表示
  1. アプリの状態管理
restorationId: state.pageKey.value
  • restorationIdを指定することで、アプリの状態復元をサポートします
  • アプリが中断から再開された際の画面状態の保持に役立ちます

ただし、以下の点に注意が必要です:

  • すべてのルートでNoTransitionPageを使用する必要はありません
  • ユーザーの操作に基づく通常の画面遷移では、適切なアニメーションを使用することでUXが向上する場合もあります
  • アプリの性質や要件に応じて、使い分けを検討してください

例えば、DetailPageではbuilderを使用していますが、これは通常の遷移アニメーションが適していると判断されているためだと考えられます:

GoRoute(
  path: RoutePath.detailId,
  name: 'detail',
  builder: (context, state) => DetailPage(
    id: state.pathParameters['id']!,
  ),
)

まとめ

画面遷移のアニメーションを無効化したいときに使うものでした。昔ボトムナビゲーションバーとリダイレクトの処理を組み合わせると発生したエラーを解決するときに使ったことあったような。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?