flutter
/

GetIt with Bloc: Dependency Injection for Scalable Flutter Apps

Last Sync: Today

On this page

12
0%
Intermediate
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutterIntermediate

GetIt with Bloc: Dependency Injection for Scalable Flutter Apps

Managing dependencies in a Flutter app becomes crucial as the project grows. GetIt is a simple service locator that can be used alongside Bloc to provide dependencies like repositories, use cases, and blocs themselves. This guide shows you how to integrate GetIt with Bloc, register dependencies, and write clean, testable code.

What is GetIt?

GetIt is a service locator (not a dependency injection container) that gives you a global object to access your dependencies. It allows you to register singletons, factories, and lazy instances, and then retrieve them anywhere in your app. When combined with Bloc, it helps decouple your UI from the creation of blocs and their dependencies.

Installation

YAMLRead-only
1
dependencies:
  get_it: ^7.6.7
  flutter_bloc: ^8.1.5

Run flutter pub get to install.

Setting Up GetIt

Create a file (e.g., lib/di/injection.dart) to register all your dependencies. You'll typically create a GetIt instance and register dependencies in a function called init().

DARTRead-only
1
import 'package:get_it/get_it.dart';
import 'package:dio/dio.dart';
import '../features/auth/data/datasources/auth_remote_datasource.dart';
import '../features/auth/data/repositories/auth_repository_impl.dart';
import '../features/auth/domain/repositories/auth_repository.dart';
import '../features/auth/domain/usecases/login.dart';
import '../features/auth/presentation/bloc/auth_bloc.dart';

final getIt = GetIt.instance;

Future<void> init() async {
  // External
  getIt.registerLazySingleton<Dio>(() => Dio());

  // Data sources
  getIt.registerLazySingleton<AuthRemoteDataSource>(
    () => AuthRemoteDataSourceImpl(getIt()),
  );

  // Repositories
  getIt.registerLazySingleton<AuthRepository>(
    () => AuthRepositoryImpl(getIt()),
  );

  // Use cases
  getIt.registerLazySingleton(() => Login(getIt()));

  // Blocs (factories because they hold state and are created per screen)
  getIt.registerFactory(() => AuthBloc(getIt()));
}

Initializing GetIt in main

DARTRead-only
1
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await init(); // Initialize GetIt
  runApp(MyApp());
}

Using GetIt in Blocs

Now you can create blocs that receive their dependencies via constructor. When you need a bloc, you can retrieve it from GetIt.

DARTRead-only
1
class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final Login loginUseCase;

  AuthBloc(this.loginUseCase) : super(AuthInitial()) {
    on<LoginEvent>(_onLogin);
  }

  // ...
}

To use this bloc in a widget, you can get it from GetIt and provide it with BlocProvider.

DARTRead-only
1
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MultiBlocProvider(
        providers: [
          BlocProvider(create: (_) => getIt<AuthBloc>()),
          BlocProvider(create: (_) => getIt<CartBloc>()),
        ],
        child: HomePage(),
      ),
    );
  }
}

Registering Different Types of Dependencies

MethodUse CaseExample
`registerSingleton`Single instance for the whole app`getIt.registerSingleton(MyService())`
`registerLazySingleton`Created on first access`getIt.registerLazySingleton(() => MyService())`
`registerFactory`New instance each time`getIt.registerFactory(() => MyBloc(getIt()))`
`registerSingletonAsync`Async initialization`getIt.registerSingletonAsync(() async => await createDb())`

Injecting Dependencies into Blocs

With GetIt, you can inject repositories and use cases directly into blocs. This keeps blocs focused and testable. For example:

DARTRead-only
1
// In injection.dart
getIt.registerFactory(() => AuthBloc(getIt<Login>(), getIt<Register>()));

// In AuthBloc
class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final Login loginUseCase;
  final Register registerUseCase;

  AuthBloc(this.loginUseCase, this.registerUseCase) : super(AuthInitial());
}

Testing with GetIt

GetIt makes testing easy because you can replace dependencies with mocks. In your test files, you can reset GetIt and register mock implementations before creating the bloc under test.

DARTRead-only
1
void main() {
  setUp(() {
    getIt.reset();
    getIt.registerLazySingleton<AuthRepository>(() => MockAuthRepository());
    getIt.registerFactory(() => Login(getIt()));
    getIt.registerFactory(() => AuthBloc(getIt()));
  });

  test('login success', () async {
    final bloc = getIt<AuthBloc>();
    // ...
  });
}

GetIt vs BlocProvider

BlocProvider is still used to provide blocs to the widget tree so that they can be accessed via context.read and automatically disposed. GetIt is used for creating the bloc instances and injecting their dependencies. The two work together: GetIt creates the bloc, and BlocProvider passes it to the widget tree.

Best Practices

  • Use registerFactory for blocs – Blocs hold state and are usually scoped to a screen; a new instance is needed each time the screen is created.
  • Use registerLazySingleton for services/repositories – These are stateless and can be reused across the app.
  • Initialize GetIt in main before runApp – Ensures all dependencies are ready before the UI builds.
  • Avoid using GetIt directly in UI code – Instead, retrieve the bloc via BlocProvider and use context.read. Keep GetIt for creation only.
  • Reset GetIt between tests – Use getIt.reset() in setUp to avoid test pollution.
  • Use getIt<MyType>() with a type hint – Explicit types help readability.

Common Mistakes

  • ❌ Registering blocs as singletons – Can cause state to persist across screens, leading to bugs. ✅ Use registerFactory for blocs.
  • ❌ Calling getIt<Bloc>() inside build methods – This may create a new instance on each rebuild if the registration is a factory. ✅ Provide the bloc via BlocProvider and use context.read.
  • ❌ Not resetting GetIt in tests – Causes dependencies to leak between tests. ✅ Call getIt.reset() in setUp.
  • ❌ Registering async dependencies without awaiting allReady – The app may start before dependencies are ready. ✅ Use getIt.allReady() after registering async dependencies.

Conclusion

GetIt is a powerful complement to Bloc for managing dependencies in Flutter apps. It centralizes dependency creation, simplifies testing, and keeps your blocs clean and focused. By combining GetIt with BlocProvider, you get the best of both worlds: a service locator for creation and InheritedWidget for scoped access. Adopt this pattern to build scalable, maintainable Flutter applications.

Test Your Knowledge

Q1
of 3

Which GetIt registration method should you use for a bloc that should be created anew each time?

A
registerSingleton
B
registerLazySingleton
C
registerFactory
D
registerSingletonAsync
Q2
of 3

What is the primary benefit of using GetIt with Bloc?

A
It makes the UI faster
B
It centralizes dependency creation and simplifies testing
C
It automatically disposes of blocs
D
It replaces the need for BlocProvider
Q3
of 3

Why should you call `getIt.reset()` in test setups?

A
To free memory
B
To prevent test pollution by clearing previous registrations
C
To initialize async dependencies
D
To speed up tests

Frequently Asked Questions

Can I use GetIt without BlocProvider?

Yes, you can retrieve blocs directly from GetIt in your widgets, but you lose the automatic disposal and scoping that BlocProvider offers. It's recommended to use BlocProvider for providing blocs to the widget tree.

Should I use GetIt or Provider for DI?

GetIt is a service locator; Provider is an InheritedWidget wrapper. Many projects use both: GetIt for creating dependencies, Provider/BlocProvider for passing them to the widget tree. They are not mutually exclusive.

How do I handle dependencies that require `BuildContext`?

If a dependency needs BuildContext (e.g., a local database that needs context), you should create it later, typically in a widget's initState or inside a BlocProvider's create method. GetIt may not be the right place for such dependencies.

Is GetIt thread‑safe?

GetIt is not thread‑safe. In Flutter, all code runs on the main isolate, so it's safe. If you use isolates, you'll need separate GetIt instances.

How do I register a dependency with parameters?

Use registerFactoryParam to register a factory that takes parameters. Then call getIt<MyType>(param1: value, param2: value).

Previous

bloc dependency injection

Next

bloc injectable

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.