What if your app could get what it needs—without building everything itself? That’s the magic of dependency injection in App.



This content originally appeared on DEV Community and was authored by Hitesh Meghwal

As mobile developers, we’ve all hit that point where our app starts small… but then features stack up, services multiply, and suddenly you’re wrestling with a monster.
That’s where Dependency Injection (DI) steps in as a lifesaver.

🔍 What is Dependency Injection in flutter?

In simple terms, DI is a design pattern that helps you supply an object’s dependencies from the outside, instead of creating them inside the object.

In this app, we’re handling Dependency Injection (DI) using the GetIt package — a popular service locator in Dart and Flutter.
GetIt acts as a central registry where you can register services (like Auth, API handlers, databases, etc.) and retrieve them anywhere in the app — without having to manually pass them down through constructors.
It’s a general-purpose DI solution designed to keep your code clean, decoupled, and scalable. Whether you’re working with state management, API layers, or storage services, GetIt helps manage those dependencies efficiently.

Let’s go with simple example:

🍽 Dependency Injection Explained Like Room Service

Let’s say you’re staying at a hotel.
Whenever you’re hungry, you don’t go to the kitchen, find the chef, gather ingredients, and cook for yourself. Instead, you just call room service.
You don’t care how the food is made — you only care that it arrives on time and works.

Now imagine you have 100 rooms and each room has its own private kitchen doing everything from scratch. That’s:

  • Expensive
  • Messy
  • Impossible to manage at scale

Instead, the hotel has:

  • One centralized kitchen (your service)
  • A delivery system (your dependency injection framework)
  • Every room request food (uses the service) when needed

Let’s see how to implement in Flutter app:

🛠 What We’ll Cover

  1. ✅ Add get_it to pubspec.yaml
  2. 📦 Define services (like AuthService)
  3. 🔁 Setup GetIt with registerSingleton and registerFactory
  4. 🧪 Use DI in UI classes
  5. 🧩 Pattern explanation: when to use factory vs singleton

  6. pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  get_it: ^8.0.3

Then run:
flutter pub get

  1. Create a Service
// lib/services/auth_service.dart
class AuthService {
  void login() {
    print("User logged in via AuthService");
  }
}
  1. Setup GetIt (Dependency Injection Configuration)
// lib/di/injection.dart
import 'package:get_it/get_it.dart';
import '../services/auth_service.dart';

final getIt = GetIt.instance;

void setupLocator() {
  // Register Singleton (one shared instance)
  getIt.registerLazySingleton<AuthService>(() => AuthService());

  // If you want a new instance every time, use registerFactory:
  // getIt.registerFactory<AuthService>(() => AuthService());
}

Now, in main.dart, initialize it:

void main() {
  setupLocator();
  runApp(MyApp());
}
  1. Using the Injected Service
// lib/screens/login_screen.dart
import 'package:flutter/material.dart';
import '../di/injection.dart';
import '../services/auth_service.dart';

class LoginScreen extends StatelessWidget {
  final AuthService _authService = getIt<AuthService>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            _authService.login();
          },
          child: Text("Login"),
        ),
      ),
    );
  }
}

When BLoC + Cubit (via BlocProvider)
• When using Flutter BLoC, you can inject dependencies via BlocProvider.
• Can be used like DI for blocs or services.

BlocProvider(
  create: (_) => AuthCubit(authService),
  child: LoginScreen(),
)
  1. When to Use registerSingleton vs registerFactory
Pattern Use Case Code Example
registerSingleton Use when the object must persist getIt.registerSingleton<AuthService>(...)
registerLazySingleton Same as singleton, but lazy-loaded getIt.registerLazySingleton<AuthService>(...)
registerFactory Use when you need a new instance each time getIt.registerFactory<AuthService>(...)

🧩 Example: Singleton vs Factory

🔁 Singleton Example (Global Auth, Shared Instance)

  • getIt.registerLazySingleton(() => AuthService()); → Same AuthService instance used across the entire app

🔄 Factory Example (Fresh Object)

  • getIt.registerFactory(() => AuthService()); → Each time you call getIt(), a new object is returned

✅ Wrap-up all things

GetIt is your app’s room service system — widgets just ask for what they need and GetIt delivers!
By using:

  • registerLazySingleton or registerSingleton for shared services (Auth, API, Storage)
  • registerFactory for short-lived or per-screen classes (Controllers, Blocs)

🧩 Conclusion

When you’re building small prototypes, you might not need DI. But as your app becomes real-world, team-based, and multi-featured, DI becomes essential.
It’s not just about “clean code” — it’s about:
• Easier maintenance
• Faster onboarding for new devs
• Confidence in refactoring
• Robust unit testing
If your app is getting complex,

dependency injection isn’t optional—it’s foundational.


This content originally appeared on DEV Community and was authored by Hitesh Meghwal