Flutter Tutorial

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

MethodYou 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

GetItProviderRiverpod
Needs BuildContextNoYesNo (uses ref)
Rebuilds UI by itselfNo (use watch_it or a builder)YesYes
Main jobFinding objectsSharing stateState + DI

Try it yourself

Register an ApiService with GetIt, then build a screen whose FutureBuilder calls getIt<ApiService>().fetchGreeting().

Practise in the playground Updated