Overview
The ApplicationContainer class is the entry point for Nest Dart applications. It manages module registration, service resolution, and provides a unified interface for accessing services across your application.
Key Features:
- Module registration and initialization
- Centralized service resolution
- Automatic dependency ordering
- Lifecycle management
- Export control and encapsulation
- Testing support with reset functionality
Class Definition
class ApplicationContainer
Constructor
ApplicationContainer([GetIt? getIt])
Creates a new application container.
Optional GetIt instance. If not provided, uses GetIt.instance.
Example:
// Use default GetIt instance
final container = ApplicationContainer();
// Use custom GetIt instance (useful for testing)
final testContainer = ApplicationContainer(GetIt.asNewInstance());
Properties
isReady
Check if the container is ready (all modules initialized).
Returns: bool - true if all modules have been initialized, false otherwise.
Example:
final container = ApplicationContainer();
await container.registerModule(AppModule());
if (container.isReady) {
print('Container is ready!');
}
modules
Get all registered modules as an unmodifiable list.
Returns: List<Module> - Immutable list of registered modules.
Example:
final container = ApplicationContainer();
await container.registerModule(UserModule());
await container.registerModule(AuthModule());
print('Registered modules: ${container.modules.length}');
context
ModuleContext get context
Get the module context (useful for testing and debugging).
Returns: ModuleContext - The internal module context tracking imports/exports.
This property is primarily useful for testing and debugging module relationships.
getIt
Get the underlying GetIt instance.
Returns: GetIt - The wrapped GetIt dependency injection container.
Direct access to GetIt bypasses module restrictions. Use with caution and prefer the container’s get() method.
Methods
registerModule
Future<void> registerModule(Module module) async
Register a module with the container. This makes the module’s exported services available to the application and triggers initialization.
The module to register with the container.
Returns: Future<void>
Process:
- Registers the module and all its dependencies
- Makes exported services globally available
- Calls
onModuleInit on the module and its dependencies
- Marks the container as ready
Example:
final container = ApplicationContainer();
// Register a single module
await container.registerModule(UserModule());
// Access exported services
final userService = container.get<UserService>();
registerModules
Future<void> registerModules(List<Module> modules) async
Register multiple modules at once.
List of modules to register.
Returns: Future<void>
Example:
final container = ApplicationContainer();
await container.registerModules([
UserModule(),
AuthModule(),
DatabaseModule(),
]);
get
T get<T extends Object>({String? instanceName})
Get a service instance from the container. Services are only accessible if they are exported by registered modules.
The type of service to retrieve.
Optional named instance identifier.
Returns: T - The requested service instance.
Throws: ServiceNotExportedException if the service is not accessible.
Example:
// Get a service by type
final userService = container.get<UserService>();
// Get a named instance
final primaryDb = container.get<Database>(instanceName: 'primary');
final secondaryDb = container.get<Database>(instanceName: 'secondary');
getWithParams
T getWithParams<T extends Object>(dynamic param1, [dynamic param2])
Get a service instance with runtime parameters (for factories registered with parameters).
The type of service to retrieve.
First parameter to pass to the factory.
Optional second parameter to pass to the factory.
Returns: T - The requested service instance.
Throws: ServiceNotExportedException if the service is not accessible.
Example:
// Register factory with params in module
locator.registerFactoryParam<UserLogger, String, LogLevel>(
(userId, logLevel) => UserLogger(userId, logLevel),
);
// Get instance with params
final logger = container.getWithParams<UserLogger>('user123', LogLevel.info);
getAsync
Future<T> getAsync<T extends Object>({String? instanceName})
Get an asynchronously initialized service instance.
The type of service to retrieve.
Optional named instance identifier.
Returns: Future<T> - The requested service instance.
Throws: ServiceNotExportedException if the service is not accessible.
Example:
// Register async singleton in module
locator.registerSingletonAsync<Database>(
() async {
final db = Database();
await db.connect();
return db;
}
);
// Get async service
final database = await container.getAsync<Database>();
isRegistered
bool isRegistered<T extends Object>({String? instanceName})
Check if a service is registered and accessible from the application container.
The type of service to check.
Optional named instance identifier.
Returns: bool - true if the service is registered and accessible.
Example:
if (container.isRegistered<UserService>()) {
final service = container.get<UserService>();
}
isRegisteredInContainer
bool isRegisteredInContainer<T extends Object>({String? instanceName})
Check if a service is registered in the underlying container, ignoring export restrictions.
The type of service to check.
Optional named instance identifier.
Returns: bool - true if the service is registered (even if not accessible).
This method checks for registration regardless of export status. Use isRegistered() to check if a service is accessible.
getAvailableServices
Set<Type> getAvailableServices()
Get all services available to the root container.
Returns: Set<Type> - Set of all accessible service types.
Example:
final services = container.getAvailableServices();
print('Available services: ${services.map((t) => t.toString()).join(", ")}');
waitUntilReady
Future<void> waitUntilReady({Duration? timeout})
Wait for the container to be ready (all modules initialized).
Optional timeout duration. Throws TimeoutException if exceeded.
Returns: Future<void>
Throws: TimeoutException if initialization exceeds the timeout.
Example:
final container = ApplicationContainer();
// Start registration in background
container.registerModule(AppModule()).ignore();
// Wait for completion with timeout
await container.waitUntilReady(
timeout: Duration(seconds: 30),
);
print('Container ready!');
allReady
Future<void> allReady({
Duration? timeout,
bool ignorePendingAsyncCreation = false,
})
Wait for all services to be ready (delegates to GetIt’s allReady method).
Optional timeout duration.
ignorePendingAsyncCreation
Whether to ignore pending async service creation.
Returns: Future<void>
Example:
await container.registerModule(AppModule());
await container.allReady(timeout: Duration(seconds: 10));
print('All services ready!');
reset
Future<void> reset() async
Reset the container, destroying all modules and clearing all registrations. Useful for testing.
Returns: Future<void>
Process:
- Calls
onModuleDestroy on all modules in reverse initialization order
- Resets the underlying GetIt container
- Clears all module registrations
- Clears module context
- Marks container as not ready
Example:
test('user service test', () async {
final container = ApplicationContainer();
await container.registerModule(UserModule());
// Run tests...
// Clean up
await container.reset();
});
Resetting the container will dispose all singleton instances. Make sure to properly handle cleanup in your module’s onModuleDestroy method.
Complete Example
import 'package:nest_core/core.dart';
// Define modules
class DatabaseModule extends Module {
@override
Future<void> providers(Locator locator) async {
locator.registerSingleton<Database>(Database());
}
@override
List<Type> get exports => [Database];
}
class UserModule extends Module {
@override
List<Module> get imports => [DatabaseModule()];
@override
Future<void> providers(Locator locator) async {
locator.registerLazySingleton<UserRepository>(
() => UserRepository(locator.get<Database>()),
);
locator.registerLazySingleton<UserService>(
() => UserService(locator.get<UserRepository>()),
);
}
@override
List<Type> get exports => [UserService];
}
class AppModule extends Module {
@override
List<Module> get imports => [UserModule()];
}
// Application entry point
void main() async {
// Create container
final container = ApplicationContainer();
// Register root module (imports UserModule -> DatabaseModule)
await container.registerModule(AppModule());
// Wait for all services to be ready
await container.allReady();
print('Container initialized in ${container.isReady}');
print('Available services: ${container.getAvailableServices()}');
// Access services
final userService = container.get<UserService>();
final users = await userService.getAllUsers();
print('Loaded ${users.length} users');
}
Testing Example
import 'package:test/test.dart';
import 'package:nest_core/core.dart';
void main() {
late ApplicationContainer container;
setUp(() async {
// Create fresh container for each test
container = ApplicationContainer(GetIt.asNewInstance());
await container.registerModule(AppModule());
});
tearDown(() async {
// Clean up after each test
await container.reset();
});
test('should access user service', () async {
final userService = container.get<UserService>();
expect(userService, isNotNull);
});
test('should throw when accessing non-exported service', () {
expect(
() => container.get<UserRepository>(),
throwsA(isA<ServiceNotExportedException>()),
);
});
}
Best Practices
- Single container instance: Create one container per application
- Register at startup: Register all modules during application initialization
- Wait for ready: Use
waitUntilReady() before accessing services
- Use isolated containers for tests: Create new GetIt instances for each test
- Reset in tearDown: Always reset the container in test tearDown
- Prefer get<T>(): Use the container’s
get() method instead of accessing GetIt directly