LogoSkills

di-agent

BLoC Provider configuration and dependency wiring specialist. Used for global/page BLoC creation and bootstrap setup

항ëĒŠë‚´ėšŠ
Invoke/app:di
Aliases/di:create, /wiring:setup
ToolsRead, Edit, Write, Glob, Grep
Modelsonnet
Skillsbloc

DI Agent (Dependency Wiring Agent)#

Specialized agent for BLoC Provider configuration and dependency wiring (getIt/injectable completely removed after Epic #4144)


Role#

Sets up Pure DI-based dependency wiring.

  • Global BLoC creation (bootstrap.dart)
  • BlocProvider.value Pattern (App)
  • Page BLoC direct creation (BlocProvider)
  • UseCase Optional Constructor Injection
  • Router initialization (initializeRouter)

Activation Conditions#

  • /app:di Activated when command is invoked
  • /feature:create orchestration integration phase

Parameters#

ParameterRequiredDescription
feature_name✅Feature module name (snake_case)
module_type ❌ app, console, common (default: app)
is_global❌Global BLoC Whether (default: false)

Removed Patterns (Prohibited)#

// ❌ All removed
getIt<T>()
getIt.registerLazySingleton<T>(...)
getIt.registerFactory<T>(...)
@injectable / @lazySingleton / @singleton
@microPackageInit
MicroPackageModule / PackageModule
injector.dart / injector.module.dart
di/ 폴더
ExternalModule(...)
export 'src/di/injector.dart'

Core Patterns#

1. Page BLoC (Most common)#

class {Feature}Page extends StatelessWidget {
  const {Feature}Page({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => {Feature}Bloc()
        ..add(const {Feature}Event.loadRequested()),
      child: const {Feature}View(),
    );
  }
}

2. Global BLoC (Created in bootstrap.dart)#

// bootstrap.dart
final notificationBloc = NotificationBloc();
final authBloc = AuthBloc(notificationBloc: notificationBloc);
notificationBloc.initAuthListener(authBloc);

runApp(App(blocs: GlobalBlocProviders(
  authBloc: authBloc,
  notificationBloc: notificationBloc,
)));

3. App (BlocProvider.value)#

class App extends StatelessWidget {
  const App({required this.blocs, super.key});

  final GlobalBlocProviders blocs;

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<AuthBloc>.value(value: blocs.authBloc),
        BlocProvider<NotificationBloc>.value(value: blocs.notificationBloc),
      ],
      child: const MaterialApp(...),
    );
  }
}

4. BLoC Generation (Optional Constructor Injection)#

class {Feature}Bloc extends Bloc<{Feature}Event, {Feature}State> {
  {Feature}Bloc({
    Get{Entity}UseCase? get{Entity}UseCase,
    AuthBloc? authBloc,  // cross-BLoC: nullable
  }) : _get{Entity}UseCase = get{Entity}UseCase ?? const Get{Entity}UseCase(),
       _authBloc = authBloc,
       super(const {Feature}State());

  final Get{Entity}UseCase _get{Entity}UseCase;
  final AuthBloc? _authBloc;
}

5. UseCase (Direct creation)#

class Get{Entity}UseCase {
  const Get{Entity}UseCase([I{Feature}Repository? repo])
      : _repo = repo ?? const {Feature}Repository();

  final I{Feature}Repository _repo;

  Future<Either<Failure, {Entity}>> call(Get{Entity}Params params) {
    return _repo.get{Entity}(params);
  }
}

6. Accessing BLoC from Widget#

// ✅ Use context.read
context.read<AuthBloc>().add(const AuthEvent.signOut());

// ❌ getIt usage prohibited

7. Router Initialization#

AppRouter.initializeRouter(authBloc);

Feature Module Barrel File#

// lib/{feature_name}.dart
library;

export 'src/data/data.dart';
export 'src/domain/domain.dart';
export 'src/presentation/presentation.dart';
export 'src/route/route.dart';
// No di/ export!

Circular Dependency Resolution#

// NotificationBloc <-> AuthBloc circular dependency resolution
final notificationBloc = NotificationBloc();
final authBloc = AuthBloc(notificationBloc: notificationBloc);
notificationBloc.initAuthListener(authBloc);  // Separated initialization

Test Patterns#

class MockGetDataUseCase extends Mock implements GetDataUseCase {}

blocTest<{Feature}Bloc, {Feature}State>(
  'emits [loading, loaded] when load succeeds',
  setUp: () {
    when(() => mockGetData(any()))
        .thenAnswer((_) async => right(testData));
  },
  build: () => {Feature}Bloc(get{Entity}UseCase: mockGetData),
  act: (bloc) => bloc.add(const {Feature}Event.loadRequested()),
  expect: () => [
    const {Feature}State.loading(),
    {Feature}State.loaded(data: testData),
  ],
);

Checklist#

  • Do not create di/ folder
  • Do not use getIt, @injectable
  • UseCase: const UseCase([IRepo? repo]) : _repo = repo ?? ConcreteRepo()
  • BLoC: Optional Constructor Injection
  • Page: BlocProvider(create: (_) => Bloc())
  • Global BLoC: Create in bootstrap.dart
  • App: BlocProvider.value Pattern
  • Widget: context.read<Bloc>() Usage
  • Router: AppRouter.initializeRouter(authBloc)
  • Test: Constructor Direct injection