Что нового?
В предыдущей статье мы рассмотрели базовые принципы работы DI-контейнера CherryPick: архитектуру, компоненты (Scope, Module, Binding), и простые примеры использования на Dart/Flutter. Сегодня расскажу о новых возможностях.
Почему Flutter и CherryPick — хороший выбор
Flutter приложения часто становятся довольно сложными: множество экранов, виджетов, сервисов. Чем больше приложение — тем актуальнее становится потребность в централизованном и масштабируемом подходе к управлению зависимостями. CherryPick позволяет:
Создавать модульные и иерархичные Scope для каждой «области жизни» (например, экрана) приложения.
Даёт лаконичный, но мощный API и расширяет его аннотациями и генерацией кода.
Позволяет внедрять зависимости в синхронном и в асинхронном режиме.
Снижает связность, облегчает тестирование и подмену зависимостей.
Быстрая интеграция CherryPick c Flutter
1. Добавляем пакеты
В pubspec.yaml
:
dependencies:
cherrypick: ^2.0.0
cherrypick_annotations: ^1.1.0-dev.0
cherrypick_flutter: ^1.1.0
dev_dependencies:
cherrypick_generator: ^1.1.0-dev.0
build_runner: ^2.4.15
Выполните:
flutter pub get
2. Описываем DI-модуль с аннотациями
CherryPick поддерживает аннотационный подход — вы просто помечаете методы и параметры в своём модуле аннотациями, и генератор создаст за вас нужный boilerplate.
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
import 'package:cherrypick/cherrypick.dart';
@module()
abstract class AppModule extends Module {
@singleton()
ApiClient apiClient() => ApiClient();
@provide()
AuthRepository repository(ApiClient api) => AuthRepository(api);
@provide()
String greeting(@params() String name) => 'Привет, $name!';
}
3. Генерируем биндинги
Запустите генерацию:
flutter pub run build_runner build
Будет создан сгенерированный класс $AppModule
, который наследует ваш AppModule и содержит все нужные биндинги для DI.
4. Интегрируемся во Flutter с помощью CherryPickProvider
В точке входа (обычно в main.dart
):
import 'package:flutter/material.dart';
import 'package:cherrypick_flutter/cherrypick_flutter.dart';
import 'app_module.cherrypick.g.dart'; // Импортируем сгенерированный модуль
void main() {
CherryPick.openRootScope().installModules([
$AppModule(),
]);
runApp(
CherryPickProvider(
child: MyApp(),
),
);
}
Компонент CherryPickProvider
регистрирует rootScope и даёт доступ к зависимостям через весь widget tree.
5. Используем зависимости в виджетах
Чтобы получить зависимости внутри виджета, вызовите:
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final rootScope = CherryPickProvider.of(context).openRootScope();
final greeting = rootScope.resolve<String>();
final repo = rootScope.resolve();
return Text(greeting);
}
}
Создаём под-Scopes для экрана
Можно использовать отдельные Scope'ы для специфичных областей приложения (например, в навигации):
final screenScope = CherryPickProvider.of(context).openSubScope(scopeName: 'myScreen');
screenScope.installModules([MyFeatureModule()]);
Как работает генерация биндингов
Аннотации нужны, чтобы избежать ручной «сборки» зависимостей (никакакого копипаста и простыней кода). Вы отмечаете методы и параметры:
@singleton
— биндинг singleton'а, живёт во всём Scope.@provide
— биндинг через toProvide().@instance
— биндинг через toInstance().@named('имя')
— привязка к ключу (например, когда в DI несколько String или int).@params
— параметр, который подаётся динамически во время resolve.
cherrypick_generator
превращает ваш модуль в код примерно такого вида:
final class $AppModule extends AppModule {
@override
void builder(Scope currentScope) {
bind<A>().toProvide(() => apiClient()).singleton();
bind<B>().toProvide(() => repository(currentScope.resolve()));
bind<C>().toProvideWithParams((args) => greeting(args));
}
}
Еще один пример: настройка роутера через DI
// В AppModule или отдельном RouterModule
@singleton()
AppRouter router() => AppRouter();
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final rootScope = CherryPickProvider.of(context).openRootScope();
return MaterialApp.router(
routerDelegate: rootScope.resolve().delegate(),
routeInformationParser: rootScope.resolve().defaultRouteParser(),
);
}
}
Как тестировать с CherryPick
CherryPick позволяет легко подменять модули/зависимости на время теста. Просто создайте отдельный Scope, установите туда mock-модуль и тестируйте в изоляции:
final testScope = CherryPick.openRootScope()
..installModules([TestModule()]);
final service = testScope.resolve();
Заключение
CherryPick — современный DI-стек для Flutter и Dart: лаконичный, расширяемый, с модульной структурой, поддержкой runtime-параметров, экспортируемых контейнеров, генератором кода и удобной интеграцией в любое приложение.
Рекомендую попробовать в pet-проектах и production.
Документация: README.md
Исходный код и примеры: CherryPick на GitHub
Вопросы и PR приветствуются!