Что нового?

В предыдущей статье мы рассмотрели базовые принципы работы 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.

Комментарии (0)