Jun 15, 2026 · 6 min read
GetIt: Dependency Injection with the Service Locator Pattern in Flutter
The more your Flutter app grows, the messier handling its dependencies gets. A new service here, a new repository there, a new view model — and before you notice it, a codebase that started out clean suddenly feels tangled. This is exactly where Dependency Injection with the Service Locator pattern earns its keep.
Before we go deeper, let's quickly cover what Dependency Injection (DI) and the Service Locator actually are. Dependency Injection is a design pattern that decouples an object or class from its dependencies (things like libraries or services). Applying it gives you a codebase that's more modular, easier to maintain, and — of course — easier to read.
The Service Locator, on the other hand, is a design pattern that provides a single, centralized registry holding every service your app needs. With this approach, an object or class just "asks" that registry for the service it wants. And here's the neat part: the Service Locator pattern can also back your Dependency Injection — acting as the container that holds all the services the app depends on.
Introducing the get_it Package
get_it is a popular library for implementing the Service Locator pattern in Flutter apps. It's simple, lightweight, and makes it easy to register and retrieve services from one central place — which is exactly what makes it a great fit for the Service Locator pattern.
Let's Set Up Our Project
To use GetIt, we first need to install the get_it package in the project root. Open pubspec.yaml and add the dependency like this:
dependencies:
flutter:
sdk: flutter
get_it: ^9.1.1
Then run:
flutter pub get
Here Goes the Fun Part
As an example, we'll wire up Provider for state management on a simple authentication use case, with this dependency flow:
FirebaseAuth → AuthRemoteDatasource → AuthRepository → SignInUsecase → AuthProvider
Next, create an injection.dart file in a (lib/di) folder and make an instance of the GetIt class. Then we can register the app's services on that GetIt instance using registerFactory or registerSingleton. Those methods are what create an instance of each service when it's requested later. The file ends up looking like this:
import 'package:get_it/get_it.dart';
// global GetIt instance
final getIt = GetIt.instance;
// Daftar service-mu disini
void setupInjection() {
getIt.registerLazySingleton<FirebaseAuth>(() => FirebaseAuth.instance);
getIt.registerLazySingleton<AuthRemoteDatasource>(() => AuthRemoteDatasource(getIt()));
getIt.registerLazySingleton<AuthRepository>(() => AuthRepository(getIt()));
getIt.registerLazySingleton<SignInUsecase>(() => SignInUsecase(getIt()));
getIt.registerFactory<AuthProvider>(() => AuthProvider(getIt()));
}
This file's job is to define the GetIt instance and expose a setupInjection() function that holds the app's services globally. This is the centralized container for every dependency we'll use across the whole app.
Now let's head over to main.dart and update the code like this:
void main() {
WidgetsFlutterBinding.ensureInitialized();
setupInjection(); //setup DI sebelum aplikasi dijalankan
runApp(
ChangeNotifierProvider(
create: (_) => getIt<AuthProvider>(), //panggil service dengan getIt
child: const MyApp(),
),
);
}
In the example above, we register a handful of dependency classes (FirebaseAuth, AuthRemoteDatasource, AuthRepository, SignInUsecase) as singletons. AuthProvider, on the other hand, we register as a factory, because it depends on several of the dependencies before it.
We also lean on GetIt to grab the AuthProvider instance (the service) inside the ChangeNotifierProvider's context, so the dependency drops straight into the widget tree with no fuss.
Let's Summarize the Results
After trying out the Service Locator pattern for Dependency Injection with GetIt, we can conclude that GetIt gives us a simple, flexible way to do Dependency Injection with the Service Locator pattern in Flutter.
With GetIt, we can easily manage a whole range of dependencies across different services. It also keeps objects and classes loosely coupled, while boosting the modularity and readability of our codebase.
As a recap, here are some of the benefits you get from using GetIt for Dependency Injection:
- Simple and lightweight
- Easy to use
- Makes managing dependencies and services much easier
- Improves code readability, maintainability, and modularity
- Works across many different contexts — both inside Flutter widgets and in business logic
To really see the benefit, let's compare our earlier use case with and without GetIt. Without GetIt, we'd obviously have to create and manage the dependencies between services manually, like this:
void main() {
runApp(
MultiProvider(
providers: [
Provider<FirebaseAuth>(
create: (_) => FirebaseAuth.instance,
),
ProxyProvider<FirebaseAuth, AuthRemoteDatasource>(
update: (_, firebaseAuth, __) => AuthRemoteDatasource(firebaseAuth),
),
ProxyProvider<AuthRemoteDatasource, AuthRepository>(
update: (_, remoteDatasource, __) => AuthRepositoryImp(remoteDatasource),
),
ProxyProvider<AuthRepository, SignInUsecase>(
update: (_, repo, __) => SignInUsecase(repo),
),
ChangeNotifierProxyProvider<SignInUsecase, AuthProvider>(
create: (context) => AuthProvider(signInUsecase: context.read<SignInUsecase>()),
update: (_, signInUsecase, authProvider) => authProvider!..updateUsecase(signInUsecase),
),
],
child: const AppWidget(),
),
);
}
Here we register the entire dependency chain by hand inside ChangeNotifierProvider(). Pretty inefficient, right? This can become a real problem down the line as the app's codebase keeps growing.
With GetIt, we can cut this whole process short by registering all of the app's services and their dependencies in the GetIt instance, like the example at the start. That lets us pull out a service instance and its dependencies whenever we need them, with no manual wiring.
Hope you found this useful. Thanks for reading!