go_router_modular 5.5.2
go_router_modular: ^5.5.2 copied to clipboard
Modular Routing and Dependency Injection for Flutter with GoRouter. Features event-driven architecture for seamless module communication and micro-frontend support.
Unreleased #
5.5.2 #
- Documentation: reworked the
READMEfor a cleaner, pub.dev-friendly layout — added an Installation section and a step-by-step Quick Start (configure, compose modules, define a feature module, navigate), a features table, and a collapsible events add-on section. The layout no longer relies on centering, which pub.dev strips. - Fixed: the
go-router-modularAgent Skill was silently skipped by theskillsCLI because a:inside itsSKILL.mdfrontmatterdescriptionproduced invalid YAML. The description was corrected so the skill is discovered and installable again.
5.5.0 #
-
Route-state reads consolidated on the
Modularfacade with better names. The read utilities moved off theBuildContextextension and onto the facade, following the...Of(context)convention. Beyond path and parameters, there is now explicit access to the usefulgo_routerutilities (query params, uri, location, and typedextra) from a single entry point:final state = Modular.routerStateOf(context); // the GoRouterState final path = Modular.currentPathOf(context); // the current path final id = Modular.pathParamOf(context, 'id'); // a path parameter final params = Modular.pathParamsOf(context); // all path parameters final ref = Modular.queryParamOf(context, 'ref'); // a query parameter final queries = Modular.queryParamsOf(context); // all query parameters final uri = Modular.currentUriOf(context); // the current Uri final location = Modular.currentLocationOf(context); // the current location final payload = Modular.extraOf<MyPayload>(context); // the typed extraThe
go_routerutilities (GoRouterState,context.go,context.push, …) are still re-exported by the package barrel, so you can use them directly by importing onlypackage:go_router_modular/go_router_modular.dart.Deprecations (still functional, will be removed in a future major release):
- Extension:
context.getPathParam('id')→Modular.pathParamOf(context, 'id');context.getPath→Modular.currentPathOf(context);context.state→Modular.routerStateOf(context). - Facade:
Modular.getCurrentPathOf(context)→currentPathOf(context);Modular.stateOf(context)→routerStateOf(context).
- Extension:
5.4.0 #
-
Simplified
EventModulecomposition; unused event APIs removed. The composition mechanism based oneventImports()+ModularEventListenerwas removed (it was dead code, with no consumers). To compose another module's listeners, callAnotherEventModule().listen()synchronously inside your ownlisten()— the child's listeners inherit the host's lifecycle (they are disposed together with it, with no duplication when recreated).class EventModuleA extends EventModule { @override void listen() { on<EventFromA>((event, context) { /* ... */ }); EventModuleB().listen(); // direct composition } }In addition, the listening logic is no longer exposed as the public
EventListenerMixinmixin — it was incorporated directly intoEventModule(which is still aModule). The only public mixin in the events subsystem is nowModularEventMixin(forState<StatefulWidget>).Migration:
- Remove
eventImports()overrides and any use ofModularEventListener; move theon<T>calls into thelisten()of anEventModuleand compose viaAnotherEventModule().listen(). - If you referenced
EventListenerMixindirectly, useEventModuleinstead.
- Remove
5.3.0 #
Added #
-
copyWithon the modular router. Override individualGoRouteroptions at the point where the router is consumed, reusing everything passed toModular.configure(...). Useful whenModularApp.routerdoesn't expose an option you need — for example attaching aNavigatorObserver:MaterialApp.router( routerConfig: Modular.routerConfig.copyWith( observers: [MyAnalyticsObserver()], ), builder: (context, child) => ModularLoader.builder(context, child), );Overridable options include
observers,redirect,refreshListenable,errorBuilder,errorPageBuilder,redirectLimit,navigatorKey,restorationScopeId, and more. The derived router is memoized, so widget rebuilds reuse the same instance and navigation state is preserved.
Changed #
- Bumped the
go_routerconstraint to^17.3.0, pulling in the chained top-level redirect resolution fix (17.2.1) and thepop()+onExitstale-configuration fix (17.2.2).
5.2.0 #
Breaking #
-
Untyped factory binds no longer auto-resolve through supertypes. Applies to any supertype relationship — interfaces, abstract classes, concrete superclasses, and mixins — not just interfaces. The previous compatibility search probed every factory bind in
bindsMapto type-check it against the requested supertype, invoking the factory's constructor (with all its side effects: event publication, stream subscription, HTTP calls, etc.) and discarding the resulting instance. Production traces showed cubits being silently instantiated multiple times per session as a byproduct of unrelatedget<T>()calls. The compatibility search now skips factory binds entirely; only singletons (whose probe instance is cached and reused) participate. Migration:// Before — works by accident, instantiates ServiceImpl as a probe side-effect i.addFactory((i) => ServiceImpl()); i.get<IService>(); // ✅ resolved // After — explicit, no probe, zero side effects until first real get i.addFactory<IService>((i) => ServiceImpl()); i.get<IService>(); // ✅ resolved via Strategy 2 (direct lookup) // Or, if singleton semantics are acceptable: i.addSingleton((i) => ServiceImpl()); i.get<IService>(); // ✅ resolved (singleton cached, probe-safe)Direct lookups by the registered concrete type (
get<ServiceImpl>()) still work for untyped factories — only the supertype auto-discovery is removed.
Fixed #
- Phantom factory instances during interface lookup (the breaking change above is the fix). Reproduction: with 4 factory binds in a module, every unrelated
get<IUnregistered>instantiated all 4 (running their constructors). After 100 lookups in a session, ~400 phantom Cubit/Service instances leaked — each potentially opening WebSockets, subscribing to streams, or firing events. Now: zero phantom invocations. - Cross-type circular dependency now surfaces a clear error.
A → B → Apreviously fell into the globalhasBlockedBindsbypass and was masked asBind not found for type "A"at the deepest probe level. The validation bypass is now tightened to the specific self-reference case (addFactory<I>((i) => i.get())) using a typed(bind, requestedType)invocation stack. Cross-type cycles now throwModularException: Circular dependency detected while resolving type "A". Dependency chain: A -> B -> A. BindRegistry.registerindexes typed binds under the declared type.Bind.factory<IService>((i) => ServiceImpl())previously stored the bind only underServiceImpl(the discovered runtime type), makingget<IService>miss Strategy 2 and depend on probing. Typed binds now occupybindsMap[IService]directly; the discovered runtime type is also indexed forget<ServiceImpl>lookups.
Improved #
BindSearchProtectionis re-entrant safe. ReplacedSet<Object> _blockedBindswith a counter-backedMap<Object, int>so nested invocations of the same bind don't prematurely "unblock" each other on the firstpop.- Single helper for factory invocations. Extracted
BindLocator._withInvocation(bind, requestedType, action)— every factory call goes through here, eliminating the previous four-way duplication ofpushInvocation / try / finally / popInvocation.
5.1.0 #
Added #
- Stateful shell branch transitions:
StatefulShellBranchTransitionshelpers (e.g.withGoTransition, fade presets) so bottom tabs/branches can reuse the same transition style as modularGoRoutes.
Improved #
- Dependency injection — batch registration: Typed binds (
Bind<T>) are now indexed up front inregisterBatch, so any bind in the same batch can resolve siblings in any declaration order.commitBatchruns in three phases: materialize singletons, propagate cached instances to duplicateBindobjects, then fall back to deferred resolution forBind<Object>registrations. - Dependency injection — code quality: Extracted
_writeToCanonicalSlotto centralise the dual-map invariant (bindsMapfor unkeyed,bindsMapByKeyfor keyed binds). Replaced the ambiguousbool _handleExistingBindwith a_SlotConflictResolutionenum. Renamed_pendingBatchto_uncommittedBatch. Swallowed registration errors now surface viadart:developer.logwhendebugLogModularis enabled.
Fixed #
- Singleton identity across interface lookups:
BindLocator._searchCompatibleBindnow reuses the canonical bind when resolving through an interface, preventing a new factory call on everyInjector.get<IFoo>()call. - Self-referential interface factory (
addFactory<I>((i) => i.get())): The locator now detects recursive resolution for the same type and skips the executing bind, falling through to the concrete implementation. Previously this threw"Type IFoo is already being searched". ConcurrentModificationErrorduring interface lookup:_searchCompatibleBindnow iterates a snapshot ofbindsMap.entriesinstead of the live view, making in-loop writes safe under nested resolution.- Singleton instantiated multiple times via imports: Duplicate
Bindobjects created by re-imported modules now receive the cached instance from the already-registered bind, preventing repeated factory calls. Injector.get<IService>()after typed registration: A singleton registered with an explicit generic (e.g.i.addSingleton<IAuthApi>(...)) is now reachable through both the interface and the concreteruntimeType.- Keyed + unkeyed singleton on the same type:
bindsMap[type]now holds only the unkeyed bind; keyed binds live exclusively inbindsMapByKey, makingInjector.get<IClient>()order-independent.
5.0.6 #
Fixed #
- Duplicate singleton construction via imports:
_collectImportedBindscreated newBindobjects withcachedInstance == nullon every registration pass.commitBatchnow propagatescachedInstanceto duplicate binds before downstream methods run, preventing 2× extra factory calls. - Orphaned singleton instances: When two imported modules declare the same type,
commitBatchnow checks_isSingletonAlreadyRegisteredbefore callingfactoryFunction, completely preventing duplicate factory calls and leaked instances (open streams, duplicate subscriptions).
5.0.5 #
Fixed #
- Dependency injection: Singleton and lazySingleton constructors were called multiple times during module registration. The system now correctly reuses the cached instance after the first creation.
5.0.4 #
Fixed #
onExitinChildRoute: Fixed propagation of theonExitcallback across all route creation paths.
5.0.3 #
Added #
- Detailed error messages: Missing dependency errors now include which component made the request and a full dependency chain (e.g.
A ➔ B ➔ C). - Circular dependency detection: Detects infinite recursion during bind resolution and surfaces a clear, actionable error message.
- Safer resource cleanup:
CleanBindnow handlesdispose,close, andcancelmore robustly, with safe fallback forNoSuchMethodError.
5.0.2 #
Improved #
- Dependency injection: Switched from nested search to a commit-based approach for better performance.
5.0.1 #
Added #
-
Bind.lazySingleton: Creates singleton instances only on first access — useful for expensive resources that may not always be needed.i.lazySingleton<ExpensiveService>((i) => ExpensiveService()); -
Page transitions: Built-in transition system for smooth route animations.
- Presets: fade, slide, scale, rotate, and more.
- Child routes automatically inherit transitions from parent modules.
- Platform-specific styles: Cupertino (iOS/macOS) and Material (Android).
- Chainable effects:
GoTransitions.slide.toRight.withFade.
ModuleRoute('/home', module: HomeModule(), transition: GoTransitions.fadeUpwards, duration: Duration(milliseconds: 300)) ChildRoute('/details', child: (_, __) => DetailsPage(), transition: GoTransitions.slide.toRight.withFade)
5.0.0 #
Breaking Changes #
-
New
bindsAPI: Changed fromFutureOr<List<Bind<Object>>> binds()toFutureBinds binds(Injector i). Binds are now registered via injector methods instead of returning a list.// Before (4.x) FutureOr<List<Bind<Object>>> binds() => [ Bind.factory<ApiService>((i) => ApiService()), Bind.singleton<DatabaseService>((i) => DatabaseService()), ]; // After (5.x) FutureBinds binds(Injector i) { i.add<ApiService>((i) => ApiService()); i.addSingleton<DatabaseService>((i) => DatabaseService()); }
Added #
- Native injector: Removed dependency on
auto_injector. Direct registration methods:i.add(),i.addSingleton(),i.addLazySingleton(). - Performance: ~4× faster dependency resolution and reduced memory overhead.
- Better error messages: Clearer type mismatch and cycle detection errors.
4.2.2 #
4.2.0+4 #
Fixed #
- Added validation to prevent overwriting already-registered singletons for the same type and key.
4.2.0 #
4.1.0 #
4.0.0 #
Breaking Changes #
-
ModularApp.router: ReplacesMaterialApp.router.routerConfigis set automatically — remove it from your code. -
Async binds and imports:
binds()andimports()now returnFutureOr<List<T>>instead ofList<T>.// Before (3.x) List<Bind<Object>> get binds => [Bind.singleton<MyService>((i) => MyService())]; List<Module> get imports => [SharedModule()]; // After (4.x) FutureOr<List<Bind<Object>>> binds() => [Bind.singleton<MyService>((i) => MyService())]; FutureOr<List<Module>> imports() => [SharedModule()];
Added #
ModularApp.router: ExtendsMaterialApp.routerwith automatic loader display during module registration.ModularLoader: Built-in loading overlay withModularLoader.show()/ModularLoader.hide()and aCustomModularLoaderabstract class for full appearance customisation.
3.0.0 #
Breaking Changes #
-
Root route required: Modules must now declare a root
ChildRoute('/')as their entry point. An assertion error is thrown if it is missing.List<ModularRoute> get routes => [ ChildRoute('/', child: (_, __) => HomePage()), // required ChildRoute('/details', child: (_, __) => DetailsPage()), ];
2.0.3+1 #
2.0.2+1 #
Added #
InternalLogsclass for consistent debug output across route registration and bind management.
Fixed #
_registerwas not being called during route redirects, causing missing dependency injection before page construction.
Improved #
- Bind registration is skipped when no redirect occurs, reducing redundant operations and log noise.