Кратко
Понадобился DI без бойлерплейта, который не позволяет достать что угодно откуда угодно. Написал два пакета: sputnik_di для dart и flutter_sputnik_di для flutter. Кайфую.
Для Dart:
dart pub add sputnik_di
Для Flutter:
dart pub add flutter_sputnik_di
По сути зависимости представляют собой оринетированный граф. DepsNode - узел зависимостей, в которых регистрируются все необходимые классы и описывает порядок их сборки.
Мотивация
Сейчас у нас на рынке есть три основных вариантов: get_it, riverpod и yx_scope. Они покрывают практически все потребности и у каждого есть свои плюсы и минусы.
get_it довольно громоздкий и тяжело работает со scope'ами, кроме того он позволяет достать любую зависимость из любого места.
riverpod я активно использую и считаю, что это один из самых удачных вариантов, если использовать его правильно. Однако по��воляет подключать любую зависимость в любой контейнер, что пугает. Кроме того, автор фреймворка впихнул туда довольно много лишних концепций, и направление развития в общем мне не нравится.
yx_scope действительно хорош, я иногда пишу на нем модули приложения и из моего опыта - приходится писать много кода. Создай scope_holder, создай контейнер. Сложно прокинуть зависимости извне. Много путаницы с базовым классом ScopeContainer. В общем порог входа туда довольно высокий, много бойлерплейта.
Захотелось создавать легковесный фреймворк с максимально простой концепцией, при этом сохранить баланс между бойлерплейтом и качеством кода. Моя мысль при создании этого пакета была такая: ошибся - сам дурак. Я рассчитываю на то, что разработчик достаточно умен, чтобы уметь структурировать зависимости по контейнерам и правильно организовывать последовательность их вызовов. Как говорится, нормально делай - нормально будет.
Концепция
Я не люблю в голове держать одновременно много вещей. Я вообще по своей сути ленивый. Поэтому хотел, чтобы запоминать надо было как можно меньше.
Есть узел зависимостей DepsNode. Все. Дальше крутите как хотите.
Ну а если погрузиться чуть глубже, DepsNode - это узел из ориентированного графа ваших зависимостей. Узел может быть геморроидальным представлять какую-либо фичу или быть так называемым скоупом. Все зависит от вас. В конце концов, все узлы формируют граф, который может быть разделен узлами-скоупами (которые по сути тоже являются DepsNode, просто мы называем его скоуп).


DepsNode представляет собой класс, в полях которого описываются зависимости. Он имеет не сложный ЖЦ.
Простой пример
Напишем простой пример с использованием sputnik_di для реализации каунтера.
class CounterDepsNode extends DepsNode { late final counterManager = bind( () => CounterManager( counterStateHolder(), ), ); late final counterStateHolder = bind( () => CounterStateHolder(), ); // Очередь этих зависимостей проинициализируется когда будет вызвано // depsNode.init(); @override List<Set<LifecycleDependency>> get initializeQueue = [ { counterStateHolder, }, ]; } class CounterManager { final CounterStateHolder _counterStateHolder; const CounterManager(this._counterStateHolder); void increaseBy(int count) { for (int i = 0; i < count; i++) { _counterStateHolder.increment(); } } } // StateHolder - встренный в фреймворк класс, хранящий в себе состояние class CounterStateHolder extends StateHolder<int> { CounterStateHolder() : super(0); void increment() { state = state + 1; } } Future<void> main() async { final counterDepsNode = CounterDepsNode(); // инициализируем узел await counterDepsNode.init(); counterDepsNode.counterManager().increaseBy(5); print(counterDepsNode.counterStateHolder().state); // выведет 5 // высвобождаем ресурсы (подписки и др) await counterDepsNode.dispose(); // вы можете очистить все созданные внутри экземпляры классов // которые были обернуты в bind или bindSingletonFactory counterDepsNode.clear(); }
Понятно, что таким образом можно вкладывать узлы друг в друга и формировать граф:
class AuthScopeDepsNode { final AppScopeDepsNode _appScopeDepsNode; AuthScopeDepsNode(this._appScopeDepsNode); late final counterDepsNode = bind(() => CounterDepsNode()); @override List<Set<LifecycleDependency>> get initializeQueue = [ { counterDepsNode, }, ]; } class AppScopeDepsNode { late final authScopeDepsNode = bind(() => AuthScopeDepsNode(this)); late final authManager = bind( () => AuthManager( authScopeDepsNode(), ) ); }
Использование с Flutter
Вернемся к примеру с CounterDepsNode и попробуем на его основе показать связку Flutter с sputnik_di.
Прокинуть DepsNode вниз по дереву DepsNodeBinder:
final counterDepsNode = CounterDepsNode(); DepsNodeBinder( depsNode: counterDepsNode, child: Builder( builder: (context) { final variant1 = context.depsNode<CounterDepsNode>(); final variant2 = DepsNodeBinder.of<CounterDepsNode>(context); ... } ), )
Билдер на основе DepsNode - DepsNodeBuilder:
DepsNodeBuilder( depsNode: counterDepsNode, bindOnInitialized: false, idle: (context, depsNode) {...}, initializing: (context, depsNode) {...}, // required initialized: (context, depsNode) {...}, disposing: (context, depsNode) {...}, disposed: (context, depsNode) {...}, // required orElse: (context, depsNode) {...}, )
Билдер на основе StateHolder, StateHolderBuilder:
final counterStateHolder = counterDepsNode.counterStateHolder(); StateHolderBuilder( holder: counterStateHolder, builder: (context, state) {...} )
Слушатель на основе StateHolder, StateHolderListener:
StateHolderListener( holder: counterStateHolder, listener: (state) { ... }, child: ..., )
Все кажется довольно просто, мы видели это много раз.
Фабрики
В фреймворке есть возможность создавать зависимости на основе параметров и выглядит это так:
class AuthScopeDepsNode { late final orderScope = bindSingletonFactory( (String orderUuid) => OrderScopeDepsNode(orderUuid), ); } class OrderScopeDepsNode { final String orderUuid; OrderScopeDepsNode(this.orderUuid); ... }
Таким образом можно хранить множество заказов друг за друг��м под одной зависимостью и получать контексты для разных заказов по необходимости.
Что с этими знаниями делать?
Все вам рассказал, круто и классно. Делаем dart pub add flutter_sputnik_di|sputnik_di и используем с удовольствием. Жду вас в своем репозиториями с фидбеком и предложениями: github.
Заключение
Получился довольно лаконичный пакет, который ничего не навязывает. При этом этот пакет подойдет для проектов разных уровней сложности. Порог входа в такой пакет минимальный. Пишите ваши предложения, оставляйте отзывы, делитесь своими примерами использования. Всем хорошего настроения, всем добра :-)
Мой тг канал: @gubin_dev
