flutter-get-it-injectable-dependency-injection
Skill8 starsChanged 48 days agoMITView on GitHub
Design, implement, review, debug, test, refactor, and optimize dependency injection in production Flutter/Dart repositories using get_it, injectable, or both. Use this skill whenever work involves adding or wiring services/repositories/API clients/databases/SDKs/Bloc/Cubit dependencies, choosing factory/singleton/lazy/async/scoped lifetimes, configuring injectable generation/modules/environments/named bindings, managing startup readiness, login/logout session scopes, disposal, DI tests/overrides, circular dependencies, or hidden GetIt.I lookups—even if the request is framed only as “register this service”, “fix DI”, “wire this dependency”, or “make startup cleaner”.
In this repository70 other files it gives its agents.
- camerawesome-flutterSkill
- dart-args-production-cliSkill
- dart-flutter-equatable-engineeringSkill
- dart-flutter-ffi-systems-engineeringSkill
- dart-metaSkill
- document-camera-frameSkill
- document-scan-flutterSkill
- figma-to-flutter-expertSkill
- flutter-animations-production-engineeringSkill
- flutter-app-hardening-reverse-engineeringSkill
- flutter-app-icon-engineeringSkill
- flutter-application-security-engineeringSkill
- flutter-app-links-deep-link-engineeringSkill
- flutter-cachingSkill
- flutter-concurrency-memory-performanceSkill
- flutter-confetti-engineeringSkill
- flutter-dart-package-engineeringSkill
- flutter-design-system-from-design-mdSkill
- flutter-dio-cache-interceptor-engineeringSkill
- flutter-dio-engineeringSkill
- flutter-doc-scannerSkill
- flutter-drift-persistence-engineeringSkill
- flutter-expert-tipsSkill
- flutter-firebase-analyticsSkill
- flutter-firebase-core-engineeringSkill
- flutter-firebase-messagingSkill
- flutter-go-router-engineeringSkill
- flutter-hive-persistence-engineeringSkill
- flutter-image-performanceSkill
- flutter-image-picker-production-engineeringSkill
- flutter-lints-static-analysis-engineeringSkill
- flutter-local-notifications-production-engineeringSkill
- flutter-memory-leak-engineeringSkill
- flutter-mobile-developmentSkill
- flutter-motion-engineeringSkill
- flutter-multi-environmentSkill
- flutter-native-splash-engineeringSkill
- flutter-package-plugin-selectionSkill
- flutter-path-provider-production-storageSkill
- flutter-performance-jank-optimizationSkill
- flutter-permission-handler-engineeringSkill
- flutter-pinputSkill
- flutter-production-build-releaseSkill
- flutter-production-engineeringSkill
- flutter-production-loggingSkill
- flutter-pro-image-editor-engineeringSkill
- flutter-secure-storage-security-engineeringSkill
- flutter-sentry-engineeringSkill
- flutter-share-plus-engineeringSkill
- flutter-skeletonizer-engineeringSkill
- flutter-squadron-concurrency-engineeringSkill
- flutter-supabase-engineeringSkill
- flutter-svg-engineeringSkill
- flutter-ui-performance-engineeringSkill
- flutter-url-launcher-engineeringSkill
- flutter-video-player-production-engineeringSkill
- flutter-webview-production-engineeringSkill
- freezed-production-engineeringSkill
- html-to-flutterSkill
- hydrated-blocSkill
- The first 60 shown.
The file
Show its rules and text
We split this skill into the rules it gives an agent
60 rules. About 0 are common, 39 are distinctive, and 21 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈39
Distinctive · estimate
This author's own choices. Worth a look.
21
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Very specific: line 13
Treat get_it as the runtime container, injectable as optional registration generation, and constructor injection as the application-facing …
- Distinctive: line 15
Do not introduce DI machinery when direct constructors are already clearer.
- Distinctive: line 20
Prefer constructor injection in application/business code.
- Very specific: line 21
Keep direct getIt<T>() access at composition/framework boundaries.
- Distinctive: line 24
Do not make everything a singleton or every Bloc/Cubit a singleton.
- Very specific: line 25
Use scopes for real session/account/workflow lifetime boundaries.
- Distinctive: line 28
Keep environment selection centralized.
- Distinctive: line 29
Use named registrations only when same-type instances genuinely coexist.
- Distinctive: line 30
Do not hide circular dependencies with locator lookup.
- Very specific: line 31
Never edit generated *.config.dart manually.
- Distinctive: line 54
every registration, scope, readiness, reset, and direct locator lookup.
- Distinctive: line 71
Do not refactor until the graph is understandable.
- Distinctive: line 87
Use direct construction for cheap pure/local helpers and transient values that do not belong to the application object graph.
- Very specific: line 101
Avoid business classes that resolve GetIt.I<T>() internally.
- Distinctive: line 103
Use method parameters for call-specific data.
- Distinctive: line 118
Do not choose singleton merely to avoid wiring constructors.
- Very specific: line 122
Use constructors/manual get_it when the graph remains easy to read.
- Very specific: line 124
Use injectable when generated ordering, modules, environments, scopes, or many transitive registrations meaningfully reduce boilerplate.
- Distinctive: line 130
Use factory params or ordinary constructor/method arguments for route IDs, workflow values, or call data.
- Distinctive: line 132
Do not turn changing IDs/user input into global singleton registrations.
- Distinctive: line 147
Prefer Future-based async registration to manual readiness signals when possible.
- Very specific: line 169
Never register from widget build, repository methods, or opportunistic business flows.
- Distinctive: line 175
Use for route/feature-local mutable objects and most Bloc/Cubit registrations.
- Very specific: line 179
Use for shared services/repositories/infrastructure that need one instance but not eager startup creation.
- Very specific: line 183
Use registerSingletonAsync for shared async creation.
- Very specific: line 185
Use dependsOn only for real dependency ordering.
- Very specific: line 187
Use registerSingletonWithDependencies when construction is synchronous but prerequisites are async.
- Very specific: line 193
Use allReady() only for the required async graph and consider a timeout.
- Very specific: line 195
Do not use manual signalReady where a Future can represent readiness.
- Distinctive: line 199
Use scopes for coherent lifetime groups:
- Distinctive: line 212
Use names for same-type objects that genuinely coexist, such as public and authenticated Dio.
- Distinctive: line 214
Use environment bindings for mutually exclusive implementations.
- Distinctive: line 220
Treat annotations as registration metadata, not architecture.
- Very specific: line 246
Do not use allReady() as a universal startup hammer.
- Distinctive: line 250
Keep app-global infrastructure separate from user-specific state.
- Distinctive: line 279
Use app-lifetime Bloc/Cubit only when state truly has application lifetime.
- Very specific: line 281
Do not inject GetIt into Bloc/Cubit business logic.
- Distinctive: line 291
Do not close app-level infrastructure from a route.
- Very specific: line 295
Treat A → B → A as a design smell.
- Very specific: line 297
Do not hide the cycle using lazy GetIt.I getters.
- Very specific: line 307
Use GetIt.asNewInstance() for an isolated graph.
- Distinctive: line 309
Use scope shadowing for bounded overrides of an existing graph.
- Distinctive: line 315
Use a generated test environment when testing environment/configuration behavior, not ordinary business logic.
- Very specific: line 317
Avoid allowReassignment as the default testing strategy because it can mask duplicate-registration mistakes.
- Distinctive: line 321
Use the repository's established generation command, commonly:
- Distinctive: line 331
After DI graph changes during Flutter development, use hot restart when the running container must be rebuilt.
- Distinctive: line 344
Do not micro-optimize ordinary type/name lookup before these costs.
- Distinctive: line 346
Do not use weak cached factories for resources requiring deterministic disposal.
- Distinctive: line 352
Check composition init, generated freshness, binding type, environment, scope, name, and async initialization.
- Distinctive: line 356
Check bootstrap called twice, widget/runtime registration, manual + generated duplication, overlapping environments, duplicate names, or …
- Very specific: line 360
Check get<T>() vs getAsync<T>(), missing awaited generated init/pre-resolution, dependsOn, manual readiness, and async cycles.
- Distinctive: line 374
Update manual/generated registration.
- Distinctive: line 375
Add a direct constructor unit test.
- Distinctive: line 377
Remove legacy locator access after consumers are migrated.
- Distinctive: line 379
Keep DI refactoring separate from unrelated domain behavior changes where possible.
- Distinctive: line 402
all required dependencies resolve.
- Distinctive: line 403
no accidental duplicate or hidden cycle.
- Distinctive: line 415
every disposable resource has a known owner.
- Distinctive: line 445
Before accepting any registration, answer:
- Distinctive: line 458
Only then choose plain construction, factory, singleton, lazy singleton, async registration, scope, module, environment binding, or name.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Flutter Dependency Injection with get_it & injectable
Mission
Build a dependency graph whose ownership, lifetime, readiness, environment, and cleanup are explicit.
Treat get_it as the runtime container, injectable as optional registration generation, and constructor injection as the application-facing dependency contract.
Do not introduce DI machinery when direct constructors are already clearer.
Invariants
- Define ownership before registration.
- Prefer constructor injection in application/business code.
- Keep direct
getIt<T>()access at composition/framework boundaries. - Centralize registration in bootstrap or explicit scope initialization.
- Choose lifetime from semantics, not convenience.
- Do not make everything a singleton or every Bloc/Cubit a singleton.
- Use scopes for real session/account/workflow lifetime boundaries.
- Distinguish construction, async initialization, and readiness.
- Define deterministic disposal for owned resources.
- Keep environment selection centralized.
- Use named registrations only when same-type instances genuinely coexist.
- Do not hide circular dependencies with locator lookup.
- Never edit generated
*.config.dartmanually. - Let narrow unit tests bypass the container.
- Optimize eager work and retained graphs before lookup micro-performance.
Reference routing
| Need | Read |
| DI choice, get_it vs injectable, lifetimes, constructor injection, modules, names, environments | references/01-composition-lifetimes.md |
| Async registration/readiness, scopes, session lifetime, disposal | references/02-async-scopes-disposal.md |
| Dio/Bloc/repository integration, startup, tests/overrides, codegen, registration diagnosis | references/03-integrations-startup-testing.md |
| Circular dependencies, performance, memory, hot reload, organization, anti-patterns | references/04-architecture-performance-antipatterns.md |
| Implementation/review/refactor workflows and production checklists | references/05-workflows-checklists.md |
Load the smallest combination that resolves the task.
Repository inspection
Before editing, inspect:
pubspec.yaml,pubspec.lock, SDK constraints, and locked DI package versions;main, bootstrap,configureDependencies,@InjectableInit;- generated
*.config.dartto understand current registrations, without editing it; - every registration, scope, readiness, reset, and direct locator lookup;
- lifecycle owners for Dio, DBs, sockets, subscriptions, caches, SDKs, Bloc/Cubit;
- environment bindings, names, modules, async/pre-resolve/dispose annotations;
- tests that override, shadow, or reset registrations;
- the repository's generation commands.
For important registrations capture:
Type / implementation
Owner / lifetime / scope
Name / environment
Async readiness
Disposal
ConsumersDo not refactor until the graph is understandable.
Dependency decision workflow
1. Identify responsibility
State what the object does. If unclear, fix the responsibility before DI design.
2. Identify the smallest real owner
Choose application, session/account/tenant, feature/workflow, route/widget subtree, or one call.
Ownership determines lifetime and cleanup.
3. Decide whether DI is needed
Use direct construction for cheap pure/local helpers and transient values that do not belong to the application object graph.
4. Make dependencies explicit
Prefer constructor injection:
final class OrdersRepository {
OrdersRepository(this.api, this.cache);
final OrdersApi api;
final Cache cache;
}Avoid business classes that resolve GetIt.I<T>() internally.
Use method parameters for call-specific data.
5. Choose lifetime
| Semantics | Registration |
| Fresh object each resolution | factory |
| Fresh object + caller runtime args | factory parameter |
| One eager/already-created object | singleton |
| One shared object on first use | lazy singleton |
| Shared async-created object | async singleton |
| Shared async object only on demand | lazy async singleton |
| Session/account/workflow lifetime | scoped registration |
| Weak GC-driven reuse with no deterministic cleanup need | cached factory, rarely |
Do not choose singleton merely to avoid wiring constructors.
6. Choose manual or generated composition
Use constructors/manual get_it when the graph remains easy to read.
Use injectable when generated ordering, modules, environments, scopes, or many transitive registrations meaningfully reduce boilerplate.
Preserve a valid existing convention unless migration solves a concrete problem.
7. Handle runtime values correctly
Use factory params or ordinary constructor/method arguments for route IDs, workflow values, or call data.
Do not turn changing IDs/user input into global singleton registrations.
8. Centralize environment selection
Bind dev/staging/prod/test implementations at composition time. Avoid scattered environment branches in repositories/services.
9. Model async behavior precisely
Ask separately:
- Does creation itself return a Future?
- Does a created object need
initialize()? - Must consumers wait?
- Must
runAppwait?
Prefer Future-based async registration to manual readiness signals when possible.
10. Define cleanup now
For sockets, DBs, streams, subscriptions, SDK clients, and other resources, decide who closes/disposes them before registration.
Factory-created resources are normally consumer-owned.
Composition root
Keep composition discoverable:
main()
→ bootstrap()
→ configureDependencies(environment)
→ await only critical readiness
→ runApp()Direct locator use is acceptable here and at narrow framework creation seams.
Never register from widget build, repository methods, or opportunistic business flows.
get_it rules
Factory
Use for route/feature-local mutable objects and most Bloc/Cubit registrations. Let the requesting owner dispose them.
Lazy singleton
Use for shared services/repositories/infrastructure that need one instance but not eager startup creation. Remember the graph remains retained after first use.
Async
Use registerSingletonAsync for shared async creation.
Use dependsOn only for real dependency ordering; keep independent startup work parallel.
Use registerSingletonWithDependencies when construction is synchronous but prerequisites are async.
Resolve lazy async registrations with getAsync<T>() unless pre-resolved.
Readiness
Use allReady() only for the required async graph and consider a timeout. Use isReady<T>() when only one dependency matters.
Do not use manual signalReady where a Future can represent readiness.
Scopes
Use scopes for coherent lifetime groups:
push authenticated session
→ register UserSession + user repositories/socket/cache
→ logout/account switch
→ pop scope and await disposalScope teardown does not automatically revoke server tokens or erase persistent data; perform domain/security cleanup separately.
Names / multiple registrations
Use names for same-type objects that genuinely coexist, such as public and authenticated Dio.
Use environment bindings for mutually exclusive implementations.
Enable getAll<T>()-style multiple registrations only for intentional extension lists.
injectable rules
Treat annotations as registration metadata, not architecture.
@injectable: factory-style generated construction.@singleton: intentional eager shared lifetime.@lazySingleton: intentional lazy shared lifetime.@module: construction of third-party/unowned dependencies.as:: abstraction binding where the interface is meaningful.@Named: genuine same-type coexistence.- environment annotations: mutually exclusive implementations.
@factoryMethod: non-default/static factory construction.@preResolve: startup must await the Future and later synchronous resolution is needed.@disposeMethod: cleanup for container-owned singleton-like objects.- scopes: use only when application flow still owns explicit scope entry/exit.
Regenerate after graph changes. Never hand-edit generated config.
Startup policy
Block runApp only for dependencies required to construct or safely use the application.
Often critical: backend-defining config, required encryption/database prerequisites, mandatory migration.
Often deferrable: analytics warm-up, optional cache warming, secondary SDKs, later feature data.
Render a bootstrap/loading state when non-critical initialization can occur after the Flutter tree starts.
Do not use allReady() as a universal startup hammer.
Session/account isolation
Keep app-global infrastructure separate from user-specific state.
Typical app scope:
- environment config;
- logger/crash reporter;
- secure credential store;
- public auth API/infrastructure.
Typical session scope:
UserSession;- authenticated API façade;
- current-user repositories/caches;
- user-specific socket/services.
On logout/account switch, stop session work, pop/await scope cleanup, clear/revoke credentials and persistent user data according to policy, then create a fresh scope for the next identity.
Bloc/Cubit integration
Typical ownership:
shared repository (lazy singleton)
→ route Cubit/Bloc (factory)
→ BlocProvider creates it
→ BlocProvider closes itUse app-lifetime Bloc/Cubit only when state truly has application lifetime.
Do not inject GetIt into Bloc/Cubit business logic.
Infrastructure integration
Register Dio, DBs, sockets, SDKs according to actual shared lifetime.
For Dio, one backend/networking policy usually maps to one shared client; use separate named/types when base URL, auth, TLS, adapter, or lifetime materially differs.
Inject interceptor dependencies through constructors.
Do not close app-level infrastructure from a route.
Circular dependencies
Treat A → B → A as a design smell.
Do not hide the cycle using lazy GetIt.I getters. Refactor responsibility, introduce a smaller shared abstraction, or move coordination to the actual workflow owner.
Testing
Narrow unit test
Construct directly with fakes. A business class should normally be testable without initializing global GetIt.
Container test
Use GetIt.asNewInstance() for an isolated graph. Await cleanup/reset.
Use scope shadowing for bounded overrides of an existing graph.
Important: shadowing a lower dependency does not rebuild an already-created singleton that captured the old dependency. Recreate/shadow the dependent object or use a fresh graph.
Generated graph test
Use a generated test environment when testing environment/configuration behavior, not ordinary business logic.
Avoid allowReassignment as the default testing strategy because it can mask duplicate-registration mistakes.
Code generation
Use the repository's established generation command, commonly:
dart run build_runner buildRegenerate after constructor, annotation, module, scope, environment, name, binding, or lifecycle changes.
CI should detect stale generated output through the repository's normal generate/format/analyze/test workflow.
After DI graph changes during Flutter development, use hot restart when the running container must be rebuilt; hot reload usually preserves runtime state.
Performance and memory
Optimize lifetime semantics:
- eager singleton → startup cost;
- lazy singleton → first-use cost + retained graph;
- factory → repeated construction but no shared retained instance;
- app singleton → retains everything reachable from it;
- session scope → releases user caches/sockets/subscriptions together;
- unnecessary readiness chains → serialize startup.
Do not micro-optimize ordinary type/name lookup before these costs.
Do not use weak cached factories for resources requiring deterministic disposal.
Failure diagnosis
Not registered
Check composition init, generated freshness, binding type, environment, scope, name, and async initialization.
Duplicate registration
Check bootstrap called twice, widget/runtime registration, manual + generated duplication, overlapping environments, duplicate names, or preserved development runtime state. Fix composition before enabling reassignment.
Async not ready
Check get<T>() vs getAsync<T>(), missing awaited generated init/pre-resolution, dependsOn, manual readiness, and async cycles.
Wrong scoped implementation
Inspect active scope, shadowing, and whether session/test scopes were pushed or popped correctly.
allReady hangs
Inspect every async/readiness registration and dependency cycle. Prefer Future-based readiness over manual signaling.
Refactoring locator-heavy code
- Add characterization tests.
- Expose hidden dependencies in constructors.
- Update manual/generated registration.
- Add a direct constructor unit test.
- Repeat one boundary at a time.
- Remove legacy locator access after consumers are migrated.
Keep DI refactoring separate from unrelated domain behavior changes where possible.
Review output
Report:
Current graph/symptom
Ownership + lifetime finding
Root cause/risk
Change made/recommended
Async/readiness impact
Scope/disposal impact
Environment/name impact
Testing performed
Startup/memory impact
Residual version-sensitive riskProduction gates
Graph and direction
- all required dependencies resolve;
- no accidental duplicate or hidden cycle;
- composition root is discoverable;
- generated config matches source;
- business classes expose constructor dependencies;
- modules contain construction, not business workflow.
Lifetime and ownership
- app/session/feature/route ownership is explicit;
- route state is not accidentally singleton;
- user-specific state ends on logout/account switch;
- shared resources are not accidentally factories;
- every disposable resource has a known owner.
Async/readiness
- required initialization is deterministic;
- startup blocks only on critical dependencies;
- sync/async resolution APIs match registration type;
- readiness cannot hang silently.
Environment/names
- one explicit environment is selected at bootstrap;
- production graph contains no fake/test binding;
- named registrations are limited and consistent.
Testing
- core units can be constructed directly;
- container tests isolate and tear down state;
- session/environment/readiness behavior is tested where material.
Performance/memory
- expensive optional services are not eagerly created without need;
- app-global retention is intentional;
- session caches/sockets/subscriptions are released;
- independent startup work is not unnecessarily serialized.
Final decision rule
Before accepting any registration, answer:
What does this object do?
Who owns it?
How long should it live?
Should consumers share it?
Does creation/readiness require await?
Who disposes it?
Does environment or runtime input change construction?
Can the consumer be unit-tested without the container?Only then choose plain construction, factory, singleton, lazy singleton, async registration, scope, module, environment binding, or name.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

