Flutter Intermediate Lesson 56 of 135 2 min read
GetIt in Flutter: Dependency Injection and Service Locator
Learn GetIt for Flutter: register singletons, lazy singletons and factories, use them without context, and combine GetIt with state management.
On this page
GetIt (package get_it) is not a state management package by itself. It is a service locator: one place where you register objects (an API client, a database, a model) and then get them from anywhere in the app, without BuildContext.
It is often combined with state management: GetIt finds the object, and ChangeNotifier, ValueNotifier, Bloc or watch_it rebuild the UI.
Install
flutter pub add get_it
Register once, use anywhere
import 'package:get_it/get_it.dart';
final getIt = GetIt.instance;
class ApiService {
Future<String> fetchGreeting() async => 'Hello from the API';
}
class AuthService {
bool loggedIn = false;
}
void setupLocator() {
getIt.registerSingleton<AuthService>(AuthService()); // created now
getIt.registerLazySingleton<ApiService>(() => ApiService()); // created on first use
}
void main() {
setupLocator();
// ...
final api = getIt<ApiService>(); // same object every time
}
Three ways to register
| Method | You get |
|---|---|
registerSingleton<T>(object) | The same object, created immediately |
registerLazySingleton<T>(() => ...) | The same object, created the first time you ask |
registerFactory<T>(() => ...) | A new object every time you ask |
GetIt + ChangeNotifier: the counter
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
final getIt = GetIt.instance;
class Counter extends ChangeNotifier {
int value = 0;
void increment() {
value++;
notifyListeners();
}
}
void main() {
getIt.registerSingleton<Counter>(Counter());
runApp(const MaterialApp(home: CounterPage()));
}
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
final counter = getIt<Counter>();
return Scaffold(
appBar: AppBar(title: const Text('GetIt')),
body: Center(
child: ListenableBuilder(
listenable: counter,
builder: (context, _) =>
Text('${counter.value}', style: const TextStyle(fontSize: 48)),
),
),
floatingActionButton: FloatingActionButton(
onPressed: counter.increment,
child: const Icon(Icons.add),
),
);
}
}
Swapping in a fake for tests
This is the biggest benefit. Your code asks for an ApiService; in tests you register a fake one instead:
class FakeApi implements ApiService {
@override
Future<String> fetchGreeting() async => 'Test greeting';
}
setUp(() {
getIt.reset();
getIt.registerSingleton<ApiService>(FakeApi());
});
watch_it: GetIt with rebuilding
The same author publishes watch_it, which adds watch functions so a widget rebuilds when a registered object changes:
class CounterText extends WatchingWidget {
const CounterText({super.key});
@override
Widget build(BuildContext context) {
final value = watchPropertyValue((Counter c) => c.value);
return Text('$value');
}
}
GetIt vs Provider vs Riverpod
| GetIt | Provider | Riverpod | |
|---|---|---|---|
Needs BuildContext | No | Yes | No (uses ref) |
| Rebuilds UI by itself | No (use watch_it or a builder) | Yes | Yes |
| Main job | Finding objects | Sharing state | State + DI |
Try it yourself
Register an ApiService with GetIt, then build a screen whose FutureBuilder calls getIt<ApiService>().fetchGreeting().