Question #96MediumFlutter Basics

Flutter injectable plugin

#flutter

Answer

Overview

Injectable is a code generation package for Dependency Injection (DI) in Flutter, built on top of

text
get_it
. It uses annotations to automatically generate DI registration code, reducing boilerplate.


Installation

Add to

text
pubspec.yaml
:

yaml
dependencies:
  injectable: ^2.3.0
  get_it: ^7.6.0

dev_dependencies:
  injectable_generator: ^2.4.0
  build_runner: ^2.4.0

Setup

1. Create Injection Configuration

lib/injection.dart:

dart
import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';

import 'injection.config.dart'; // Generated file

final getIt = GetIt.instance;

()
void configureDependencies() => getIt.init();

2. Initialize in main.dart

dart
import 'injection.dart';

void main() {
  configureDependencies(); // Register all dependencies
  runApp(MyApp());
}

3. Generate Code

bash
flutter pub run build_runner build --delete-conflicting-outputs

This generates

text
injection.config.dart
with all registrations.


Core Annotations

@singleton — Single Instance

Creates one instance for the app lifetime.

dart
import 'package:injectable/injectable.dart';


class AuthService {
  String? _token;

  void login(String token) {
    _token = token;
  }

  bool get isLoggedIn => _token != null;
}

// Usage
final authService = getIt<AuthService>();
authService.login('abc123');

@lazySingleton — Lazy Initialization

Creates singleton only when first accessed.

dart

class DatabaseService {
  DatabaseService() {
    print('Database initialized'); // Only prints when first used
  }
}

@injectable — Factory (New Instance Each Time)

Creates a new instance on every request.

dart

class UserRepository {
  Future<User> fetchUser(String id) async {
    // Each call gets new repository instance
  }
}

// Usage
final repo1 = getIt<UserRepository>(); // New instance
final repo2 = getIt<UserRepository>(); // Another new instance

Constructor Injection

Injectable automatically resolves constructor dependencies.

dart

class ApiClient {
  final String baseUrl;

  ApiClient(('baseUrl') this.baseUrl); // Named parameter
}


class UserRepository {
  final ApiClient apiClient;

  UserRepository(this.apiClient); // Auto-injected

  Future<User> getUser(String id) async {
    return apiClient.get('/users/$id');
  }
}

// Usage
final userRepo = getIt<UserRepository>(); // ApiClient auto-injected

Named Dependencies

Use

text
@Named
for multiple implementations.

dart
// Register named strings

abstract class AppModule {
  ('baseUrl')
  String get baseUrl => 'https://api.example.com

  ('apiKey')
  String get apiKey => 'secret_key_123';
}

// Inject named dependencies

class ApiService {
  final String baseUrl;
  final String apiKey;

  ApiService(
    ('baseUrl') this.baseUrl,
    ('apiKey') this.apiKey,
  );
}

Modules — Third-Party Dependencies

Use

text
@module
to register external classes (you don't own).

dart
import 'package:http/http.dart' as http;


abstract class NetworkModule {
  
  http.Client get httpClient => http.Client();

  
  Dio get dio => Dio(BaseOptions(
    baseUrl: 'https://api.example.com
    connectTimeout: 5000,
  ));
}

// Usage
final client = getIt<http.Client>();
final dio = getIt<Dio>();

Environments

Register dependencies for specific environments (dev, prod, test).

dart
// Development-only service
(env: [Environment.dev])
class MockApiService implements ApiService {
  // Fake data for testing
}

// Production service
(env: [Environment.prod])
class RealApiService implements ApiService {
  // Real API calls
}

// Initialize with environment
void main() {
  configureDependencies(environment: Environment.prod);
  runApp(MyApp());
}

Async Dependencies

Register dependencies that require async initialization.

dart

abstract class AsyncModule {
   // Wait for this before app starts
  
  Future<SharedPreferences> get prefs => SharedPreferences.getInstance();
}

// In main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await configureDependencies(); // Await async dependencies
  runApp(MyApp());
}

Dispose Pattern

dart

class DatabaseService implements Disposable {
  Database? _db;

  Future<void> init() async {
    _db = await openDatabase('app.db');
  }

  
  void onDispose() {
    _db?.close(); // Cleanup
  }
}

// GetIt calls onDispose() when resetting
getIt.reset(); // Calls onDispose on all Disposable services

Full Example

lib/services/auth_service.dart:

dart

class AuthService {
  String? _token;

  void login(String token) => _token = token;
  bool get isLoggedIn => _token != null;
}

lib/repositories/user_repository.dart:

dart

class UserRepository {
  final ApiClient apiClient;

  UserRepository(this.apiClient);

  Future<User> getUser(String id) async {
    return apiClient.get('/users/$id');
  }
}

lib/blocs/user_bloc.dart:

dart

class UserBloc {
  final UserRepository repository;
  final AuthService authService;

  UserBloc(this.repository, this.authService);

  Future<User> loadUser(String id) async {
    if (!authService.isLoggedIn) throw Exception('Not logged in');
    return repository.getUser(id);
  }
}

lib/main.dart:

dart
void main() {
  configureDependencies();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final userBloc = getIt<UserBloc>(); // Fully injected

    return MaterialApp(home: HomeScreen(userBloc: userBloc));
  }
}

Build Runner Commands

bash
# One-time generation
flutter pub run build_runner build --delete-conflicting-outputs

# Watch mode (auto-rebuild on changes)
flutter pub run build_runner watch --delete-conflicting-outputs

# Clean generated files
flutter pub run build_runner clean

Comparison: get_it vs Injectable

Featureget_it (Manual)Injectable
RegistrationManual codeAuto-generated
BoilerplateHighLow ✅
Type safetyRuntimeCompile-time ✅
DependenciesManual wiringAuto-resolved ✅
Third-party classesManual
text
@module

Best Practices

dart
// ✅ Use singleton for stateful services

class AuthService { ... }

// ✅ Use lazySingleton for expensive init

class DatabaseService { ... }

// ✅ Use injectable for stateless services

class UserRepository { ... }

// ✅ Use modules for third-party dependencies

abstract class ThirdPartyModule { ... }

// ✅ Named params for multiple implementations
('dev')

class DevApiService implements ApiService { ... }

// ❌ Don't use injectable for UI widgets (use Provider/Riverpod)

When to Use Injectable

  • Large apps with many services
  • Clean architecture (separate layers)
  • Testability (mock dependencies easily)
  • Type-safe DI (compile-time checks)
  • Small apps (overkill — use Provider)
  • Simple state (use Riverpod/BLoC)

Learn more: Injectable Package