👋

go_routerで自作Classを渡す方法

2023/11/11に公開

はじめに

go_router v12.1.0より、codecを使って$extraをシリアル化・逆シリアル化する方法が提供されました。

この方法を利用することによって、今までできなかった画面遷移時に自作Classを渡す処理を実装できます。

従来の画面間情報共有

<FirstRoute>(path: '/')

class FirstRoute extends GoRouteData {
  const FirstRoute();

  
  Widget build(BuildContext context, GoRouterState state) {
    return FirstPage();
  }
}

<SecondRoute>(path: '/second')

class SecondRoute extends GoRouteData {
  const SecondRoute({
    required this.id,
    required this.name,
  });

  final String id;
  final String name;

  
  Widget build(BuildContext context, GoRouterState state) {
    return SecondPage(id: id, name: name);
  }
}

// 画面遷移
SecondRoute(id: id, name: name).push<void>(context);

上記のように、次の画面へ送りたいデータは別々でパラメーターとして定義する必要があります。

このように実装すると、データ構造が複雑になればなるほど、コードが読みづらくなりバグも発生しやすくなるという懸念点が存在します。

ちょっと気持ち悪いですね…では$extraを使ったらどうなる?

$extraを使ったら行けるのでは?

<FirstRoute>(path: '/')

class FirstRoute extends GoRouteData {
  const FirstRoute();

  
  Widget build(BuildContext context, GoRouterState state) {
    return FirstPage();
  }
}

<SecondRoute>(path: '/second')

class SecondRoute extends GoRouteData {
  const SecondRoute(this.$extra);

  final Person $extra;

  
  Widget build(BuildContext context, GoRouterState state) {
    return SecondPage($extra);
  }
}

// 画面遷移
final person = Person(id: id, name: name);
SecondRoute(person).push<void>(context);

もう解決できたんじゃないですか!?

はい、解決できました。

が、また別の問題が発生しました。

Flutter公式のIssueによると、Widgetの状態が変化したら$extraの値がnullになる場合があります。

https://github.com/flutter/flutter/issues/99099

https://github.com/flutter/flutter/issues/137248

そのため、$extraの挙動が不安定という課題がまだ残ってます。

そしてこの課題を解決したのは、今回登場されたcodecです。

codec+$extraを使った画面間情報共有

// Codec
class ExtraCodec extends Codec<Object?, Object?> {
  const ExtraCodec();

  
  Converter<Object?, Object?> get decoder => const _Decoder();

  
  Converter<Object?, Object?> get encoder => const _Encoder();
}

class _Decoder extends Converter<Object?, Object?> {
  const _Decoder();
  
  Object? convert(Object? input) {
    if (input == null) {
      return null;
    }
    final List<Object?> objects = input as List<Object?>;
    // 各パラメーターの設定
    // パラメーターが増えるたびにswitch-caseを追加
    switch (objects[0]) {
      case 'Person':
        return Person(
          id: objects[1]! as String,
          name: objects[2]! as String,
        );
    }
    throw FormatException('Unable to parse input: $input.');
  }
}

class _Encoder extends Converter<Object?, Object?> {
  const _Encoder();
  
  Object? convert(Object? input) {
    if (input == null) {
      return null;
    }
    // 各パラメーターの設定
    // パラメーターが増えるたびにswitch-caseを追加
    switch (input) {
      case Person _:
        return [
          'Person', // 各パラメーターの一意識別子
          input.id,
          input.name,
        ];
      default:
        throw FormatException('Cannot encode type ${input.runtimeType}.');
    }
  }
}

// codec設定
final router = GoRouter(
  extraCodec: const RouteExtraCodec(),
  // ...
);

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: router,
      // ...
    );
  }
}

// 画面遷移
final person = Person(id: id, name: name);
SecondRoute(person).push<void>(context);

実装が若干ややこしくなりましたが、

Codecの設定を追加することで$extraのシリアル化・逆シリアル化が可能になり、

上記の不具合を解消できました。

さいごに

今回はcodec$extraを利用して画面遷移時に自作Classを渡す方法を紹介しました。

しかし、この方法はあくまでも一時的な手段と考えているため、また新しい技が生まれる可能性が高いと思います。

今後も引き続き動向を監視します。

GitHubで編集を提案

Discussion