Есть ли опыт работы с внедрением зависимостей (DI)?

Ответ

Да, я использовал различные подходы к внедрению зависимостей во Flutter для создания тестируемого и слабосвязанного кода.

1. Использование пакета get_it (Service Locator): Применял в проектах, где нужен был простой и глобальный доступ к сервисам.

// Настройка локатора (обычно в main.dart или отдельном файле)
final getIt = GetIt.instance;

void setupDependencies() {
  // Регистрируем синглтон (один экземпляр на всё приложение)
  getIt.registerSingleton<ApiService>(ApiService());
  // Регистрируем фабрику (новый экземпляр при каждом запросе)
  getIt.registerFactory<LoginBloc>(() => LoginBloc(getIt<ApiService>()));
  // Регистрируем ленивый синглтон (создается при первом запросе)
  getIt.registerLazySingleton<SharedPreferencesService>(
    () => SharedPreferencesService(),
  );
}

// Использование в коде
class MyRepository {
  final ApiService _api = getIt<ApiService>(); // Внедрение через локатор
  Future<Data> fetchData() => _api.getData();
}

2. Использование provider/riverpod (управление состоянием с DI): Эти пакеты не только управляют состоянием, но и естественным образом решают задачу DI через контекст.

// Настройка провайдеров с Riverpod
final apiServiceProvider = Provider<ApiService>((ref) => ApiService());
final repositoryProvider = Provider<MyRepository>(
  (ref) => MyRepository(ref.watch(apiServiceProvider)),
);

// Использование в виджете
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final repository = ref.watch(repositoryProvider); // Зависимость получена
    // ...
  }
}

Почему это важно:

  • Тестируемость: Легко подменить реальный ApiService на мок в тестах.
  • Слабая связность: Классы не создают свои зависимости жестко, а получают их извне.
  • Управление жизненным циклом: DI-контейнер (get_it или riverpod) контролирует, когда создавать и пересоздавать экземпляры (синглтон, фабрика).

В сложных проектах также рассматривал решения вроде injectable для кодогенерации на основе аннотаций.

Ответ 18+ 🔞

А, ну это про внедрение зависимостей во Flutter! Да, сталкивался, конечно. Ёпта, без этого сейчас никуда — если хочешь код, который не развалится от одного чиха и который можно нормально потестировать, без DI как без рук, честно говоря.

Первый подход — через get_it, это типа сервис-локатор. Использовал, когда нужно было, чтобы сервисы были доступны отовсюду, как вода в кране. Просто, быстро, но, блядь, доверия к такому подходу иногда ноль — слишком глобально, можно нагородить говна.

// Настраиваешь эту штуку где-нибудь при старте (в main.dart или отдельно)
final getIt = GetIt.instance;

void setupDependencies() {
  // Регистрируешь синглтон — один экземпляр на всё приложение, как батя на районе
  getIt.registerSingleton<ApiService>(ApiService());
  // Фабрика — новый экземпляр каждый раз, когда просишь. Свежак, так сказать.
  getIt.registerFactory<LoginBloc>(() => LoginBloc(getIt<ApiService>()));
  // Ленивый синглтон — создаётся только когда впервые попросят. Хитрая жопа, экономит ресурсы.
  getIt.registerLazySingleton<SharedPreferencesService>(
    () => SharedPreferencesService(),
  );
}

// А потом в любом месте кода просто берёшь, что надо
class MyRepository {
  final ApiService _api = getIt<ApiService>(); // Вот тебе и внедрение, ёб твою мать
  Future<Data> fetchData() => _api.getData();
}

Второй способ — через provider или riverpod. Эти ребята, они не только за состояние отвечают, но и за зависимости заодно. Очень удобно, всё через контекст течёт. Особенно riverpod — там вообще красота, волнение ебать, как хорошо.

// Объявляешь провайдеры с Riverpod
final apiServiceProvider = Provider<ApiService>((ref) => ApiService());
final repositoryProvider = Provider<MyRepository>(
  (ref) => MyRepository(ref.watch(apiServiceProvider)), // Смотри, как зависимость прокидывается
);

// В виджете используешь
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final repository = ref.watch(repositoryProvider); // Всё, репозиторий с нужным сервисом внутри
    // Дальше работаешь...
  }
}

А зачем весь этот цирк, спросишь? Да похуй, шучу. На самом деле, причины овердохуища:

  • Тестируемость: Хочешь потестить логику? Без проблем! В тестах подменяешь реальный ApiService на заглушку-мок, и всё, ты король. Никаких проблем с сетью или базой.
  • Слабая связность: Классы перестают быть упрямыми ослами и сами себе ничего не создают. Они получают готовое извне. Если что-то сломалось — ищешь в одном месте, а не по всему коду.
  • Управление жизненным циклом: Контейнер (будь то get_it или провайдер) решает, когда объект родить, а когда убить. Синглтон живёт вечно, фабрика — одноразовая, ленивый — пока не попросят. Красота!

В особо навороченных проектах ещё и injectable применял — это когда кодогенерация на основе анноций делает за тебя всю грязную работу. Выглядит как магия, но, блядь, работает.