modularity_core 0.1.0
modularity_core: ^0.1.0 copied to clipboard
Core implementation of Modularity framework providing dependency injection container and module lifecycle management.
modularity_core #
Core implementation of the Modularity framework, providing Dependency Injection (DI) container and Module Lifecycle management.
Features #
- SimpleBinder: A lightweight, platform-agnostic DI container
- ModuleController: Manages module lifecycle (init, dispose, hot reload)
- Scoped Injection: Hierarchical injectors with parent/child scopes
- Import/Export System: Clean separation between public and private dependencies
- Circular Dependency Detection: Prevents infinite loops in module graphs
Installation #
dependencies:
modularity_core: ^0.0.1
Usage #
Defining a Module #
import 'package:modularity_core/modularity_core.dart';
class AuthModule extends Module {
@override
void binds(Binder binder) {
// Private dependencies (internal to module)
binder.registerLazySingleton<AuthRepository>(() => AuthRepositoryImpl());
binder.registerLazySingleton<AuthService>(
() => AuthService(binder.get<AuthRepository>()),
);
}
@override
void exports(Binder binder) {
// Public dependencies (available to importers)
binder.registerLazySingleton<AuthService>(() => binder.get<AuthService>());
}
}
Using ModuleController #
void main() async {
final registry = <ModuleRegistryKey, ModuleController>{};
final controller = ModuleController(AuthModule());
await controller.initialize(registry);
final authService = controller.binder.get<AuthService>();
// Cleanup
await controller.dispose();
}
Module Imports #
class ProfileModule extends Module {
@override
List<Module> get imports => [AuthModule()];
@override
List<Type> get expects => [AuthService]; // Validates imports
@override
void binds(Binder binder) {
final authService = binder.get<AuthService>(); // From AuthModule
binder.registerLazySingleton<ProfileService>(
() => ProfileService(authService),
);
}
}
Lifecycle Hooks #
class MyModule extends Module {
@override
void binds(Binder binder) { /* ... */ }
@override
Future<void> onInit() async {
// Called after binds() and exports()
print('Module initialized');
}
@override
Future<void> onDispose() async {
// Called on dispose
print('Module disposed');
}
}
Hot Reload Support #
// Rebind all dependencies without recreating the controller
controller.hotReload();
Behind the scenes ModuleController switches the underlying binder into
RegistrationStrategy.preserveExisting, so singleton instances survive the
rebind while factories are refreshed with the latest code.
Scoped overrides #
Use ModuleOverrideScope when you need to override dependencies for imported
modules only during tests or hot reload:
final overrides = ModuleOverrideScope(children: {
AuthModule: ModuleOverrideScope(
selfOverrides: (binder) {
binder.registerLazySingleton<AuthApi>(() => FakeAuthApi());
},
),
});
await testModule(
DashboardModule(),
(module, binder) {
// ...
},
overrideScope: overrides,
);
Overrides run after binds and before exports, and they are re-applied
automatically during hot reload.
Interceptors #
class LoggingInterceptor implements ModuleInterceptor {
@override
void onInit(Module module) => print('Init: ${module.runtimeType}');
@override
void onLoaded(Module module) => print('Loaded: ${module.runtimeType}');
@override
void onError(Module module, Object error) => print('Error: $error');
@override
void onDispose(Module module) => print('Disposed: ${module.runtimeType}');
}
final controller = ModuleController(
MyModule(),
interceptors: [LoggingInterceptor()],
);
SimpleBinder API #
| Method | Description |
|---|---|
registerFactory<T>() |
New instance each call |
registerLazySingleton<T>() |
Single instance, created on first access |
registerSingleton<T>() |
Single instance, created immediately |
get<T>() |
Get dependency (throws if not found) |
tryGet<T>() |
Get dependency (returns null if not found) |
parent<T>() |
Get from parent scope only |
License #
MIT License - see LICENSE for details.