get-it-expert
Expert guidance on get_it service locator and dependency injection for Flutter/Dart. Covers registration (singleton, factory, lazy, async), scopes with shadowing, async initialization with init() pattern, retrieval, testing with scope-based mocking, and production patterns. Use when working with get_it, dependency injection, service registration, scopes, or async initialization.
Install
mkdir -p .claude/skills/get-it-expert && curl -L -o skill.zip "https://mcp.directory/api/skills/download/4653" && unzip -o skill.zip -d .claude/skills/get-it-expert && rm skill.zipInstalls to .claude/skills/get-it-expert
About this skill
get_it Expert - Service Locator & Dependency Injection
What: Type-safe service locator with O(1) lookup. Register services globally, retrieve anywhere without BuildContext. Pure Dart, no code generation.
CRITICAL RULES
- Register all services BEFORE
runApp() pushNewScope()is synchronous. UsepushNewScopeAsync()for async initpopScope()IS async (returnsFuture<void>)allReady()returnsFuture<void>- await it or use FutureBuilder/watch_it- Dispose callbacks are a parameter on registration methods, not separate methods
- Once async singletons are initialized (after
allReady()), access them with normalgetIt<T>()- nogetAsyncneeded - If using watch_it, a global
dialias forGetIt.Iis already provided - usedi<T>()instead ofgetIt<T>()
Registration
final getIt = GetIt.instance;
void configureDependencies() {
// Singleton - created immediately
getIt.registerSingleton<ApiClient>(ApiClient());
// Singleton with dispose callback
getIt.registerSingleton<StreamController>(
StreamController(),
dispose: (c) => c.close(),
);
// Lazy singleton - created on first access
getIt.registerLazySingleton<Database>(() => Database());
// Factory - new instance every call
getIt.registerFactory<Logger>(() => Logger());
// Factory with parameters
getIt.registerFactoryParam<Logger, String, void>(
(tag, _) => Logger(tag),
);
// Named instances - use when registering multiple instances of the same type
getIt.registerSingleton<Config>(devConfig, instanceName: 'dev');
getIt.registerSingleton<Config>(prodConfig, instanceName: 'prod');
}
Async Initialization
Preferred pattern: Give services a Future<T> init() method that returns this. This keeps initialization logic inside the class and allows concise registration:
class DatabaseService {
late final Database _db;
Future<DatabaseService> init() async {
_db = await Database.open('app.db');
return this; // Always return this
}
}
void configureDependencies() {
// init() pattern - concise, self-contained initialization
getIt.registerSingletonAsync<DatabaseService>(
() => DatabaseService().init(),
);
// With dependency ordering
getIt.registerSingletonAsync<ApiClient>(
() => ApiClient().init(),
dependsOn: [DatabaseService],
);
// Sync factory that needs async dependencies
getIt.registerSingletonWithDependencies<AppModel>(
() => AppModel(getIt<ApiClient>()),
dependsOn: [ApiClient],
);
}
Retrieval
final api = getIt<ApiClient>(); // get<T>() - throws if missing
final api = getIt.maybeGet<ApiClient>(); // returns null if missing
final api = await getIt.getAsync<ApiClient>(); // waits for async registration
final all = getIt.getAll<PaymentProcessor>(); // all instances of type
final config = getIt<Config>(instanceName: 'dev'); // named instance
final logger = getIt<Logger>(param1: 'MyClass'); // factory with params
Scopes
// Push scope (synchronous init)
getIt.pushNewScope(
scopeName: 'user-session',
init: (getIt) {
getIt.registerSingleton<UserData>(currentUser);
getIt.registerLazySingleton<UserPrefs>(() => UserPrefs(currentUser.id));
},
);
// Push scope (async init)
await getIt.pushNewScopeAsync(
scopeName: 'user-session',
init: (getIt) async {
final prefs = await UserPrefs.load(currentUser.id);
getIt.registerSingleton<UserPrefs>(prefs);
},
);
// Pop scope (always async - calls dispose callbacks)
await getIt.popScope();
// Pop multiple scopes
await getIt.popScopesTill('base-scope', inclusive: false);
// Drop specific scope by name
await getIt.dropScope('user-session');
// Query scopes
getIt.hasScope('user-session'); // bool
getIt.currentScopeName; // String?
Scope shadowing: Scopes are a stack of registration layers. When you register a type in a new scope that already exists in a lower scope, the new registration shadows (hides) the original. getIt<T>() always searches top-down, returning the first match. Popping a scope removes its registrations and restores access to the shadowed ones below. This is what makes scopes useful for testing (push a scope with mocks, pop it in tearDown), for user sessions (push user-specific services that shadow defaults), and for grouping related objects that should be disposed together based on business logic (e.g., push a scope for a shopping cart - popping it disposes all cart-related services at once).
Ready State
// Wait for ALL async registrations
await getIt.allReady(timeout: Duration(seconds: 10));
// Wait for specific type
await getIt.isReady<Database>(timeout: Duration(seconds: 5));
// Synchronous checks (no waiting)
getIt.allReadySync(); // bool
getIt.isReadySync<Database>(); // bool
UI integration: Use FutureBuilder with getIt.allReady() to show a splash screen while async services initialize. If using watch_it, prefer its allReady() function inside a WatchingWidget instead (see watch-it-expert skill).
Reference Counting
For scenarios like recursive navigation (same page pushed multiple times):
// Registers only if not already registered, increments ref count
getIt.registerSingletonIfAbsent<PageData>(() => PageData(id));
// Decrements ref count, disposes only when count reaches 0
getIt.releaseInstance<PageData>(ignoreReferenceCount: false);
Utility Methods
getIt.isRegistered<ApiClient>(); // bool
getIt.unregister<ApiClient>(); // remove registration
getIt.resetLazySingleton<Database>(); // recreate on next access
getIt.resetLazySingletons(inAllScopes: true); // bulk reset
getIt.checkLazySingletonInstanceExists<Database>(); // is it instantiated?
getIt.reset(); // clear everything (for tests)
getIt.allowReassignment = true; // allow overwriting registrations
getIt.enableRegisteringMultipleInstancesOfOneType(); // allow unnamed multiples
Anti-Patterns
// ❌ Accessing async service before allReady()
configureDependencies();
final db = getIt<Database>(); // THROWS - not ready yet
// ✅ Wait first
await getIt.allReady();
final db = getIt<Database>(); // Safe
// ❌ await on pushNewScope (it's void, not Future)
await getIt.pushNewScope(scopeName: 'x'); // Won't compile
// ✅ Use pushNewScopeAsync for async init
await getIt.pushNewScopeAsync(
scopeName: 'x',
init: (getIt) async { ... },
);
// OR use synchronous pushNewScope without await
getIt.pushNewScope(scopeName: 'x', init: (getIt) { ... });
Testing
// Option 1: Scope-based (preferred) - mocks shadow real registrations
setUp(() {
GetIt.I.pushNewScope(
init: (getIt) {
getIt.registerSingleton<ApiClient>(MockApiClient());
},
);
});
tearDown(() async {
await GetIt.I.popScope();
});
// Option 2: Hybrid constructor injection (optional convenience)
class MyService {
final ApiClient api;
MyService({ApiClient? api}) : api = api ?? getIt<ApiClient>();
}
// Test: MyService(api: MockApiClient())
Production Patterns
Two-phase DI (base + throwable scope):
void setupBaseServices() {
di.registerSingleton<ApiClient>(createApiClient());
di.registerSingleton<CacheManager>(WcImageCacheManager());
}
Future<void> setupThrowableScope() async {
di.pushNewScope(scopeName: 'throwableScope');
di.registerLazySingletonAsync<StoryManager>(
() async => StoryManager().init(),
dispose: (m) => m.dispose(),
dependsOn: [UserManager],
);
}
// On error recovery: reset throwable scope
await di.popScopesTill('throwableScope', inclusive: true);
await setupThrowableScope();
Logout / scope cleanup — use popScopesTill to pop multiple scopes at once instead of manually checking and popping each one:
// ❌ Manual scope-by-scope popping
void onLogout() {
if (di.hasScope('chat')) di.popScope();
if (di.hasScope('auth')) di.popScope();
}
// ✅ Use popScopesTill to pop everything above (and including) the auth scope
Future<void> onLogout() async {
if (di.hasScope('auth')) {
await di.popScopesTill('auth', inclusive: true);
}
}
More by flutter-it
View all →You might also like
flutter-development
aj-geddes
Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
drawio-diagrams-enhanced
jgtolentino
Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.
godot
bfollington
This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.
nano-banana-pro
garg-aayush
Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.
ui-ux-pro-max
nextlevelbuilder
"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."
rust-coding-skill
UtakataKyosui
Guides Claude in writing idiomatic, efficient, well-structured Rust code using proper data modeling, traits, impl organization, macros, and build-speed best practices.
Stay ahead of the MCP ecosystem
Get weekly updates on new skills and servers.