Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Dependency Injection в Dart и Flutter
Что такое Dependency Injection
DI — это паттерн, при котором объект получает свои зависимости извне, вместо того чтобы создавать их сам. Это позволяет сделать код более тестируемым, модульным и гибким.
Основной принцип
// ❌ Плохо — жёсткая связанность
class UserService {
final ApiClient api = ApiClient(); // создаём сам
final DatabaseRepository db = DatabaseRepository();
Future<User> getUser(String id) async {
return await api.fetchUser(id);
}
}
// ✅ Хорошо — инъекция зависимостей
class UserService {
final ApiClient api;
final DatabaseRepository db;
UserService({required this.api, required this.db}); // получаем извне
Future<User> getUser(String id) async {
return await api.fetchUser(id);
}
}
Теперь UserService не зависит от конкретных реализаций — они передаются при создании.
DI контейнеры в Dart
1. GetIt (самый популярный)
import 'package:get_it/get_it.dart';
final getIt = GetIt.instance;
void setupServiceLocator() {
// Регистрируем синглтоны
getIt.registerSingleton<ApiClient>(ApiClient());
getIt.registerSingleton<DatabaseRepository>(DatabaseRepository());
// Регистрируем фабрики (создают новый объект каждый раз)
getIt.registerFactory<UserService>(
() => UserService(api: getIt<ApiClient>(), db: getIt<DatabaseRepository>())
);
}
// Использование
class HomePage extends StatelessWidget {
final userService = getIt<UserService>();
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: userService.getUser("123"),
builder: (context, snapshot) => ...,
);
}
}
2. Riverpod (современный подход, интегрирован с Provider)
import 'package:riverpod/riverpod.dart';
// Провайдер для API клиента
final apiClientProvider = Provider((ref) => ApiClient());
// Провайдер для сервиса, зависит от apiClientProvider
final userServiceProvider = Provider((ref) {
final api = ref.watch(apiClientProvider);
return UserService(api: api);
});
// Провайдер для асинхронных операций
final userProvider = FutureProvider((ref) async {
final userService = ref.watch(userServiceProvider);
return await userService.getUser("123");
});
// Использование в виджете
class HomePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider);
return userAsync.when(
data: (user) => Text(user.name),
loading: () => CircularProgressIndicator(),
error: (err, stack) => Text("Error: $err"),
);
}
}
3. Provider (классический подход)
import 'package:provider/provider.dart';
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<ApiClient>(() => ApiClient()),
ProxyProvider<ApiClient, UserService>(
update: (context, api, _) => UserService(api: api),
),
],
child: MaterialApp(
home: HomePage(),
),
);
}
}
// Использование
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final userService = context.read<UserService>();
// или
final userService = Provider.of<UserService>(context, listen: false);
return ...
}
}
Типы регистрации зависимостей
1. Singleton — один экземпляр на всё приложение
getIt.registerSingleton<Config>(Config());
2. Lazy Singleton — создаётся при первом обращении
getIt.registerLazySingleton<Database>(() => Database());
3. Factory — новый экземпляр при каждом запросе
getIt.registerFactory<UserDTO>(() => UserDTO());
4. Lazy Factory — фабрика с ленивой инициализацией
getIt.registerLazySingleton<Logger>(() => LoggerImpl());
Преимущества DI
- Тестируемость — легко подменить реальный объект на мок
- Модульность — компоненты слабо связаны
- Переиспользуемость — сервисы не привязаны к конкретным реализациям
- Гибкость — можно менять реализации без изменения кода, который их использует
Тестирование с DI
test('UserService should fetch user from API', () async {
// Создаём моки
final mockApi = MockApiClient();
final mockDb = MockDatabaseRepository();
when(mockApi.fetchUser('123'))
.thenAnswer((_) async => User(id: '123', name: 'John'));
// Инъецируем моки
final service = UserService(api: mockApi, db: mockDb);
// Тестируем
final user = await service.getUser('123');
expect(user.name, 'John');
verify(mockApi.fetchUser('123')).called(1);
});
Когда использовать какой подход
- GetIt — для простых проектов, quick setup
- Riverpod — для modern, reactive приложений с сложной логикой
- Provider — компромисс между простотой и функциональностью
В современном Flutter я бы выбрал Riverpod за его мощь и типобезопасность.