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

/app:di โ€” ๋ถ€ํ’ˆ ์—ฐ๊ฒฐ ๋‹ด๋‹น#

ํ•œ๋งˆ๋””๋กœ#

์•ฑ์„ ์ด๋ฃจ๋Š” ์—ฌ๋Ÿฌ ๋ถ€ํ’ˆ(ํ™”๋ฉดยท๊ธฐ๋Šฅยท๋ฐ์ดํ„ฐ)์„ ์„œ๋กœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์—ฐ๊ฒฐํ•ด ์ฃผ๋Š” "์ „๊ธฐ ๋ฐฐ์„ " ๋‹ด๋‹น์ž…๋‹ˆ๋‹ค. ๋ ˆ๊ณ  ๋ธ”๋ก์„ ๋”ฐ๋กœ๋”ฐ๋กœ ๋งŒ๋“ค์–ด ๋‘๋ฉด ์†Œ์šฉ์—†๋“ฏ, ์ด ๋ช…๋ น์ด ๋ธ”๋ก๋“ค์„ ์ œ ์œ„์น˜์— ๋ผ์›Œ ๋งž์ถฐ ์•ฑ์ด ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

๋ˆ„๊ฐ€ยท์–ธ์ œ ์“ฐ๋‚˜์š”#

  • ์ƒˆ ๊ธฐ๋Šฅ์„ ๋งŒ๋“  ๋’ค, ๊ทธ ๊ธฐ๋Šฅ์ด ์•ฑ ์ „์ฒด์™€ ์ œ๋Œ€๋กœ ์ด์–ด์ง€๋„๋ก "์—ฐ๊ฒฐ ์ž‘์—…"์ด ํ•„์š”ํ•œ ๊ฐœ๋ฐœ์ž
  • /app:di ๋ช…๋ น์„ ์ง์ ‘ ์‹คํ–‰ํ•  ๋•Œ
  • ๊ธฐ๋Šฅ ์ „์ฒด ์ž๋™ ์ƒ์„ฑ(/feature:create)์ด ์ง„ํ–‰๋˜๋Š” ๋„์ค‘, ๋ถ€ํ’ˆ์„ ํ•ฉ์น˜๋Š” ๋‹จ๊ณ„์—์„œ ์ž๋™์œผ๋กœ ํ˜ธ์ถœ๋  ๋•Œ

๋ฌด์—‡์„ ํ•ด์ฃผ๋‚˜์š”#

์—ฐ๊ฒฐ ๋ฐฉ์‹์„ ์˜›๋‚  ๋ฐฉ์‹(getIt/injectable)์—์„œ ๊น”๋”ํ•œ ์ƒˆ ๋ฐฉ์‹์œผ๋กœ ํ†ต์ผํ•ด ์ค๋‹ˆ๋‹ค. ๊ตฌ์ฒด์ ์œผ๋กœ๋Š”:

  • ์•ฑ ์ „์ฒด์—์„œ ๊ณต์šฉ์œผ๋กœ ์“ฐ๋Š” ๊ธฐ๋Šฅ(์˜ˆ: ๋กœ๊ทธ์ธ AuthBloc, ์•Œ๋ฆผ NotificationBloc)์„ ์•ฑ ์‹œ์ž‘ ์ง€์ (bootstrap.dart)์—์„œ ๋งŒ๋“ค์–ด ์—ฐ๊ฒฐ
  • ๊ฐœ๋ณ„ ํ™”๋ฉด์ด ์ž๊ธฐ ๊ธฐ๋Šฅ์„ ์ง์ ‘ ๋งŒ๋“ค์–ด ์“ฐ๋„๋ก ๋ฐฐ์„ 
  • ์„œ๋กœ ์˜์กดํ•˜๋Š” ๋ถ€ํ’ˆ ๊ฐ„์˜ "๊ผฌ์ž„(์ˆœํ™˜ ์˜์กด)" ๋ฌธ์ œ๋ฅผ ํ’€์–ด์คŒ
  • ์ด ๋ชจ๋“  ๊ทœ์น™์„ ์ง€์ผฐ๋Š”์ง€ ์ ๊ฒ€ํ•  ์ฒดํฌ๋ฆฌ์ŠคํŠธ ์ œ๊ณต

์–ด๋–ป๊ฒŒ ์“ฐ๋‚˜์š”#

# ๊ธฐ๋ณธ ์‹คํ–‰
/app:di

# ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ๋ถ€๋ฅด๋Š” ๋‹ค๋ฅธ ์ด๋ฆ„(๋ณ„์นญ)๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
/di:create
/wiring:setup

์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’:

  • feature_name (ํ•„์ˆ˜) โ€” ์—ฐ๊ฒฐํ•  ๊ธฐ๋Šฅ ๋ชจ๋“ˆ ์ด๋ฆ„ (snake_case ํ˜•์‹)
  • module_type (์„ ํƒ) โ€” app / console / common ์ค‘ ์„ ํƒ (๊ธฐ๋ณธ๊ฐ’ app)
  • is_global (์„ ํƒ) โ€” ์•ฑ ์ „์ฒด ๊ณต์šฉ ๊ธฐ๋Šฅ์ธ์ง€ ์—ฌ๋ถ€ (๊ธฐ๋ณธ๊ฐ’ false)

์•ˆ์—์„œ ๋ฌด์Šจ ์ผ์ด ๋ฒŒ์–ด์ง€๋‚˜์š”#

  1. ๊ธˆ์ง€๋œ ์˜›๋‚  ๋ฐฉ์‹ ์ œ๊ฑฐ โ€” getIt, @injectable ๊ฐ™์€ ๋” ์ด์ƒ ์“ฐ์ง€ ์•Š๋Š” ์—ฐ๊ฒฐ ์ฝ”๋“œ๋ฅผ ์“ฐ์ง€ ์•Š๋„๋ก ๋ง‰์Šต๋‹ˆ๋‹ค.
  2. ํ™”๋ฉด๋ณ„ ๊ธฐ๋Šฅ ์—ฐ๊ฒฐ โ€” ๊ฐ ํ™”๋ฉด์ด ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์„ ์ง์ ‘ ์ƒ์„ฑํ•ด ๋ผ์›Œ ๋„ฃ์Šต๋‹ˆ๋‹ค.
  3. ๊ณต์šฉ ๊ธฐ๋Šฅ ์—ฐ๊ฒฐ โ€” ์•ฑ ์‹œ์ž‘ ์ง€์ ์—์„œ ๋กœ๊ทธ์ธยท์•Œ๋ฆผ ๊ฐ™์€ ๊ณต์šฉ ๋ถ€ํ’ˆ์„ ๋งŒ๋“ค๊ณ  ์•ฑ ์ „์ฒด์— ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.
  4. ๊ผฌ์ž„ ํ’€๊ธฐ โ€” ์„œ๋กœ ๋งž๋ฌผ๋ฆฐ ๋ถ€ํ’ˆ(์•Œ๋ฆผ โ†” ๋กœ๊ทธ์ธ)์˜ ์ˆœํ™˜ ์˜์กด์„ ๋ถ„๋ฆฌํ•ด ์•ˆ์ „ํ•˜๊ฒŒ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
  5. ์ ๊ฒ€ โ€” ๋ชจ๋“  ๊ทœ์น™(๊ธˆ์ง€ ํŒจํ„ด ๋ฏธ์‚ฌ์šฉ, ์˜ฌ๋ฐ”๋ฅธ ์ƒ์„ฑ ๋ฐฉ์‹ ๋“ฑ)์„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋กœ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

โš™๏ธ ์ƒ์„ธ ์˜ต์…˜ยท์‹คํ–‰ ๋ช…์„ธ (๊ฐœ๋ฐœ์ž / AI ์—์ด์ „ํŠธ์šฉ)

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)#

Authoritative DI paths (verify against the kobic monorepo):

  • ServiceLocator (get_it ๋Œ€์ฒด) โ†’ package/core/lib/src/app/service_locator.dart
  • App-level wiring / ServiceLocator.instance.registerSingleton(...) โ†’ app/kobic/lib/bootstrap.dart and app/kobic_console/lib/bootstrap.dart
  • โŒ No package/core/lib/src/di/ folder โ€” .../di/app_service_locator.dart and .../di/console_service_locator.dart do not exist.
// app/kobic/lib/bootstrap.dart  (console: app/kobic_console/lib/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