pushed 0.4.0 copy "pushed: ^0.4.0" to clipboard
pushed: ^0.4.0 copied to clipboard

Scoped dependencies for go_router routes using get_it. Manage route-specific dependencies with automatic lifecycle management.

pushed #

pub package likes points platforms license style: very_good_analysis codecov


Scoped dependencies for go_router routes using get_it.

Each route entry on the navigation stack gets its own get_it scope. The scope is created — and its dependencies registered — when the route enters the stack, and disposed when it leaves. Pushing the same route twice yields two independent scopes, and nested routes resolve their parent's dependencies down the scope stack.

Usage #

Add pushed to pubspec.yaml:

dependencies:
  pushed: ^0.4.0
  go_router: ^17.1.0
  get_it: ^9.2.1

Create a ScopeObserver, attach it to your GoRouter, and describe each route's scope with withScope:

final observer = ScopeObserver();

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/products',
      builder: (context, state) => const ProductsPage(),
    ).withScope(
      observer: observer,
      scopeInitializer: (getIt) {
        getIt.registerSingleton<ProductService>(ProductService());
      },
      scopeDisposer: (getIt) async {
        await getIt<ProductService>().dispose();
      },
    ),
  ],
);

observer.attach(router);

attach immediately diffs the router's current navigation state, so entries already present (for example an initial deep link) get their scopes created. Pages then resolve their dependencies from GetIt.instance:

class ProductsPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final service = GetIt.instance<ProductService>();
    return Text('Hello ${service.name}');
  }
}

For a complete working example, see the example app.

Scope lifecycle #

On every navigation, the observer diffs routerDelegate.currentConfiguration.matches against the previous stack:

  • Matches that entered the stack get their entry scope created synchronously, so dependencies exist before the page builds.
  • Matches that left the stack get their scope disposed by name via GetIt.dropScope, running the scopeDisposer.

Shell scopes (ShellRoute and StatefulShellRoute) are keyed by the shell route object rather than match identity, so the shell's scope survives branch switches and navigation between its children. Attach withScope to the shell route itself; per-branch scoping is not supported.

Best practices #

  • Use scopeDisposer for cleanup. Register the inverse of every scopeInitializer, or use registerSingleton(dispose:) — not both, which would dispose the service twice.
  • Nested routes inherit resolution, not ownership. A child entry resolves its parent's services through the get_it scope stack, but each entry's scope registers only what that page needs.
  • Test teardown and logout are different contracts. See the teardown section below.

Teardown #

pushed exposes two teardown contracts, on both the observer and the manager: a reset that runs no disposers (test teardown) and a per-ledger disposal that runs them (logout).

scopeObserver.reset() — test teardown, no disposers #

Calls GetIt.reset(dispose: false) and clears the observer's live scope state. The recipe registry (.withScope) survives, so the observer keeps creating scopes from the same recipes on the next navigation. The reset is global to the manager's GetIt instance: it wipes scopes owned by other managers sharing it. For tests, inject an isolated GetIt (GetIt.asNewInstance) into the observer's RouteScopeManager so each test starts clean.

scopeObserver.disposeAllScopes() — logout, runs disposers #

Runs the scopeDisposer of every scope the observer's manager created, then clears the observer's live state so hasActiveScope reports false immediately. Only this manager's scopes are affected; other managers sharing the same GetIt are untouched. Use for logout or production teardown.

Manager-level teardown #

The same two contracts one level down. scopeManager.resetAllScopes() resets the manager's GetIt instance without running disposers; scopeManager.disposeAllScopes() drops this manager's ledger entries by name, running disposers. Prefer the observer-level methods when an observer is attached, since scopeObserver.disposeAllScopes() also clears the observer's bookkeeping.

Troubleshooting #

GetIt.get<MyService>() not found #

  • Make sure the observer is attached: observer.attach(router).
  • Register the service in the route's scopeInitializer.
  • Verify you are on that route — a scope only exists while its entry is on the stack.

Contributing #

Contributions are welcome — see CONTRIBUTING.md. Release history is in CHANGELOG.md.

License #

MIT.

1
likes
150
points
4
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Scoped dependencies for go_router routes using get_it. Manage route-specific dependencies with automatic lifecycle management.

Repository (GitHub)
View/report issues
Contributing

Topics

#go-router #dependency-injection #get-it #scoped-dependencies #flutter

License

MIT (license)

Dependencies

equatable, flutter, get_it, go_router, meta

More

Packages that depend on pushed