Dependency connection guide for adding new Feature modules (Pure DI - getIt/injectable removed)
Architecture Change (Epic #4144)#
Removed:
di/folder (injector.dart, injector.module.dart)-
@injectable,@lazySingleton,@singleton,@microPackageInitannotations GetIt-based DI (completely removed from feature code)ExternalModule,PackageModule, Service Locator patterngetIt<T>()calls
New Pattern:
-
Global BLoC: Create directly in
bootstrap.dart-> Pass to App viaGlobalBlocProviders - Page BLoC: Create directly via
BlocProvider(create: (_) => MyBloc()) - UseCase:
UseCase([IRepo? repo]) : _repo = repo ?? ConcreteRepo()pattern - BLoC access from Widget:
context.read<AuthBloc>()
Checklist#
1. Feature Module Structure (No di/ folder!)#
feature/{type}/{feature_name}/lib/src/
โโโ data/
โ โโโ repository/
โโโ domain/
โ โโโ entity/
โ โโโ repository/ # I{Feature}Repository (interface)
โ โโโ usecase/
โโโ presentation/
โ โโโ bloc/
โโโ route/
2. UseCase Pattern (Optional Constructor Injection)#
class GetDataUseCase {
/// [GetDataUseCase]๋ฅผ ์์ฑํฉ๋๋ค.
const GetDataUseCase([IDataRepository? repo])
: _repo = repo ?? const DataRepository();
final IDataRepository _repo;
Future<Either<Failure, Data>> call(GetDataParams params) {
return _repo.getData(params);
}
}
3. BLoC Pattern (Optional Constructor Injection)#
class MyBloc extends Bloc<MyEvent, MyState> {
MyBloc({
GetDataUseCase? getDataUseCase,
AuthBloc? authBloc, // cross-BLoC ์์กด์ฑ: nullable
}) : _getDataUseCase = getDataUseCase ?? const GetDataUseCase(),
_authBloc = authBloc,
super(const MyState());
final GetDataUseCase _getDataUseCase;
final AuthBloc? _authBloc;
}
4. Providing BLoC in Page#
class MyPage extends StatelessWidget {
const MyPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => MyBloc()
..add(const MyEvent.loadRequested()),
child: const MyView(),
);
}
}
5. Global BLoC (bootstrap.dart)#
// bootstrap.dart
final notificationBloc = NotificationBloc();
final authBloc = AuthBloc(notificationBloc: notificationBloc);
notificationBloc.initAuthListener(authBloc);
// Pass to App
runApp(App(blocs: GlobalBlocProviders(
authBloc: authBloc,
notificationBloc: notificationBloc,
)));
6. App (BlocProvider.value)#
// App
MultiBlocProvider(
providers: [
BlocProvider<AuthBloc>.value(value: blocs.authBloc),
BlocProvider<NotificationBloc>.value(value: blocs.notificationBloc),
],
child: const MaterialApp(...),
)
7. Accessing BLoC from Widget#
// โ
Use context.read
context.read<AuthBloc>().add(const AuthEvent.signOut());
// โ getIt<AuthBloc>() prohibited
8. 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!
9. Router Initialization#
// late static final + initializeRouter pattern
AppRouter.initializeRouter(authBloc);
10. Tests (Direct Injection)#
class MockGetDataUseCase extends Mock implements GetDataUseCase {}
blocTest<MyBloc, MyState>(
'emits [loading, loaded] when load succeeds',
setUp: () {
when(() => mockGetData(any()))
.thenAnswer((_) async => right(testData));
},
build: () => MyBloc(getDataUseCase: mockGetData),
act: (bloc) => bloc.add(const MyEvent.loadRequested()),
expect: () => [
const MyState.loading(),
MyState.loaded(data: testData),
],
);
Common Mistakes#
Mistake 1: Using getIt#
// โ getIt usage prohibited in Feature code
create: (_) => getIt<MyBloc>(),
// โ
Direct creation
create: (_) => MyBloc(),
Mistake 2: Calling getIt in UseCase#
// โ Getting Repository via getIt prohibited
IDataRepository get _repo => getIt<IDataRepository>();
// โ
Direct creation via constructor default
const GetDataUseCase([IDataRepository? repo])
: _repo = repo ?? const DataRepository();
Mistake 3: Creating di/ folder#
// โ Creating di/ folder prohibited
feature/{type}/{feature_name}/lib/src/di/injector.dart
// โ
No di/ folder
feature/{type}/{feature_name}/lib/src/
โโโ data/
โโโ domain/
โโโ presentation/
โโโ route/
Build Commands#
# Full build (build_runner - Freezed, etc.)
melos run build
# Build specific package only
cd feature/{module_type}/{feature_name}
dart run build_runner build --delete-conflicting-outputs