Что нового?
В предыдущей статье мы рассмотрели базовые принципы работы DI-контейнера CherryPick: архитектуру, компоненты (Scope, Module, Binding), и простые примеры использования на Dart/Flutter. Сегодня расскажу о новых возможностях.
Почему Flutter и CherryPick — хороший выбор
Flutter приложения часто становятся довольно сложными: множество экранов, виджетов, сервисов. Чем больше приложение — тем актуальнее становится потребность в централизованном и масштабируемом подходе к управлению зависимостями. CherryPick позволяет:
Создавать модульные и иерархичные Scope для каждой «области жизни» (например, экрана) приложения.
Даёт лаконичный, но мощный API и расширяет его аннотациями и генерацией кода.
Позволяет внедрять зависимости в синхронном и в асинхронном режиме.
Снижает связность, облегчает тестирование и подмену зависимостей.
Быстрая интеграция CherryPick c Flutter
1. Добавляем пакеты
В pubspec.yaml:
environment: sdk: ^3.5.2 dependencies: cherrypick: ^2.2.0 cherrypick_annotations: ^1.1.0 cherrypick_flutter: ^1.1.0 dev_dependencies: build_runner: 2.4.15 cherrypick_generator: ^1.1.0
Выполните:
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.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 приветствуются!
