go_router_extra_codec_generator

Automatically generates codec registries for GoRouter extra parameters with type-safe serialization and state restoration support.

Platform Pub Package License: MIT


Why?

When using GoRouter with complex extra objects:

  • ❌ Manual factory registry management is tedious and error-prone
  • ❌ State restoration requires proper serialization setup
  • ❌ Type information is lost during serialization

This package:

  • ✅ Auto-generates factory registries from annotations
  • ✅ Creates type-safe encoder/decoder with Codec<Object?, Object?>
  • ✅ Preserves type information for proper deserialization
  • ✅ Supports state restoration out of the box

Installation

dependencies:
  go_router_extra_codec_annotation: ^1.1.0

dev_dependencies:
  go_router_extra_codec_generator: ^1.1.0

Run: flutter pub get

Usage

1. Define Base Class and Encoder/Decoder

Create a base class for all Extra objects and implement encoder/decoder:

import 'dart:convert';
import 'package:go_router_extra_codec_annotation/annotation.dart';

// Base class for all Extra objects
abstract class BasePageExtra {
  String get nameType;
  Map<String, dynamic> toJson();
  const BasePageExtra();
}

// Encoder: Serialize objects with type information
@GoRouterExtraEncoder()
class MyExtraEncoder extends Converter<Object?, Object?> {
  final Map<String, dynamic Function(Map<String, dynamic>)> factories;
  const MyExtraEncoder(this.factories);

  @override
  Object? convert(Object? input) {
    if (input == null || input is num || input is String || input is bool) {
      return input;
    }
    try {
      final typeName = input is BasePageExtra
          ? input.nameType
          : input.runtimeType.toString();
      if (factories.containsKey(typeName)) {
        final data = input is BasePageExtra
            ? input.toJson()
            : (input as dynamic).toJson();
        print('Encoding extra of type: $typeName with data: $data');
        return <String, dynamic>{'__type': typeName, 'data': data};
      }
    } catch (_) {}
    return input;
  }
}

// Decoder: Deserialize objects using type information
@GoRouterExtraDecoder()
class MyExtraDecoder extends Converter<Object?, Object?> {
  final Map<String, dynamic Function(Map<String, dynamic>)> factories;
  const MyExtraDecoder(this.factories);

  @override
  Object? convert(Object? input) {
    if (input is Map && input.containsKey('__type')) {
      try {
        final typeName = input['__type'];
        final rawData = input['data'];
        final factory = factories[typeName];

        if (factory != null && rawData is Map) {
          final typedData = Map<String, dynamic>.from(rawData);
          return factory(typedData);
        }
      } catch (_) {}
    }
    return input;
  }
}

2. Create Extra Classes with Annotations

import 'package:go_router_extra_codec_annotation/annotation.dart';
import 'package:json_annotation/json_annotation.dart';

part 'details_page.g.dart';

@GoRouterPageExtra(name: "DetailsPageExtra")
@JsonSerializable()
class DetailsPageExtra extends BasePageExtra {
  final String data;

  const DetailsPageExtra({required this.data});

  factory DetailsPageExtra.fromJson(Map<String, dynamic> json) =>
      _$DetailsPageExtraFromJson(json);

  @override
  Map<String, dynamic> toJson() => _$DetailsPageExtraToJson(this);

  @override
  String get nameType => "DetailsPageExtra";
}

💡 Tips:

  • Use @GoRouterPageExtra(name: "...") to prevent minification issues
  • Extend BasePageExtra for consistent type handling
  • Use json_serializable for automatic JSON methods

3. Run Code Generator

dart run build_runner build --delete-conflicting-outputs

This generates router_extra_codec.gen.dart:

/// Auto-generated registry for Extra classes
final Map<String, dynamic Function(Map<String, dynamic>)>
    generatedRouterExtraFactories = {
  'DetailsPageExtra': (json) => DetailsPageExtra.fromJson(json),
};

/// Codec instance with auto-generated factories
final generatedGoRouterExtraCodec =
    GoRouterExtraCodec(generatedRouterExtraFactories);

4. Configure GoRouter

final router = GoRouter(
  navigatorKey: rootNavigatorKey,
  restorationScopeId: "root_router", // ⚠️ Required for state restoration
  extraCodec: generatedGoRouterExtraCodec, // 🎯 Use generated codec
  initialLocation: "/tab1",
  routes: [...],
);

For StatefulShellRoute with state restoration:

@TypedStatefulShellRoute<MainShellRouteData>(...)
class MainShellRouteData extends StatefulShellRouteData {
  static const String $restorationScopeId = 'mainShellRoute'; // ⚠️ Add this
  // ...
}

5. Setup MaterialApp

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      restorationScopeId: "my_app", // ⚠️ Required for state restoration
      routerConfig: router,
    );
  }
}

6. Use in Routes

// Navigate with extra
DetailsRouteData($extra: DetailsPageExtra(data: 'Test data 1')).push(context);

Configuration (Optional)

Customize output location via build.yaml:

targets:
  $default:
    builders:
      go_router_extra_codec_generator:
        enabled: true
        generate_for:
          include:
            - lib/page/*_page.dart
            - lib/page/router.dart
        options:
          output_filename: router_extra_codec.gen.dart
          output_folder: lib/generated/router

Options:

  • output_filename: Name of generated file (default: router_extra_codec.gen.dart)
  • output_folder: Output directory (default: lib/generated/router)
  • generate_for: Specify which files to scan for annotations

Example

See the example folder for a complete working implementation demonstrating:

  • Multiple Extra classes with @GoRouterPageExtra annotation
  • State restoration on web (browser back/forward)
  • Custom encoder/decoder implementation
  • Integration with json_serializable
  • StatefulShellRoute with tabs

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Author

Created by @KienHT


If this package helps you, please give it a ⭐ on GitHub!