depend is a library for dependency management in Flutter applications. It provides a convenient way to initialize and access services and repositories via InheritedWidget.
- Dependency Initialization: Prepare all dependencies before the app launches.
- Global Access: Access dependencies from anywhere in the widget tree.
- Parent Dependencies Support: Easily create nested or connected dependencies.
- Ease of Use: Integrate the library into existing code with minimal changes.
- Features π
- Table of Contents
- Installation
- Usage Examples
- Example App
- Migration Guide
- Code Coverage
Add the library to the pubspec.yaml of your project:
dependencies:
depend: ^latest_versionInstall the dependencies:
flutter pub getCreate a class that extends DependencyContainer and initialize your dependencies:
class RootContainer extends DependencyContainer {
final ApiService apiService;
RootContainer({required this.apiService});
void dispose() {
apiService.dispose();
}
}Create a class that extends DependencyContainer and initialize your dependencies:
class RootDependencyFactory extends DependencyFactory<RootContainer> {
Future<RootContainer> create() async {
return RootContainer(
apiService: await ApiService.initialize(),
);
}
// or
RootContainer create() {
return RootContainer(
apiService: ApiService.initialize(),
);
}
}Wrap your app in a DependencyScope to provide dependencies:
void main() {
runApp(
DependencyScope<RootContainer, RootFactory>(
factory: RootFactory(),
placeholder: const Center(child: CircularProgressIndicator()),
builder: (BuildContext context) => const MyApp(),
),
);
}You can now access the dependency using DependencyProvider anywhere in the widget tree:
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final apiService = DependencyProvider.of<RootContainer>(context).apiService;
return FutureBuilder(
future: apiService.getData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
return Text('Data: ${snapshot.data}');
},
);
}
}final RootContainer dep = await RootFactory().create();
DependencyProvider<RootContainer>(
dependency: dep,
builder: () => YourWidget(),
// or
child: YourWidget(),
)
class YourWidget extends StatelessWidget {
@override
Widget build(BuildContext) {
root = DependencyProvider.of<RootContainer>(context);
...
}
}DependencyScope<RootContainer, RootFactory>(
factory: RootFactory(),
builder: (BuildContext context) => Text('Inject'),
placeholder: Text('Placeholder'),
errorBuilder: (Object? error) => Text('Error'),
),The scope creates the container once and disposes it when removed. Rebuilds keep it, so factory: RootFactory() inside build is safe. To get a new container, change the key: key: ValueKey(userId).
The library provides LazyGet and LazyFutureGet classes for lazy initialization of services. Dependencies are created only when they are first accessed, which improves application startup time.
class RootContainer extends DependencyContainer {
RootContainer({required this.apiService});
// Service will be created only when accessed for the first time
final LazyGet<ApiService> apiService;
}
class RootFactory extends DependencyFactory<RootContainer> {
@override
RootContainer create() => RootContainer(
apiService: LazyGet(ApiService.new),
);
}
// Usage in widgets
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final container = DependencyProvider.of<RootContainer>(context);
// ApiService is created only at this moment
final api = container.apiService.instance;
return Text('Service initialized: $api');
}
}A factory that returns null runs again on the next access: LazyGet caches only non-null values.
class RootContainer extends DependencyContainer {
RootContainer({required this.database});
final LazyFutureGet<DatabaseService> database;
}
class RootFactory extends DependencyFactory<RootContainer> {
@override
RootContainer create() => RootContainer(
database: LazyFutureGet(DatabaseService.initialize),
);
}Read it in a widget that keeps the Future between builds, or from your logic:
final database = await container.database.instance;Concurrent reads share one load. If the load fails, the next instance access tries again.
Pass a dispose callback and call dispose() on each lazy field from the container:
class RootContainer extends DependencyContainer {
RootContainer({required this.database, required this.apiService});
final LazyFutureGet<DatabaseService> database;
final LazyGet<ApiService> apiService;
@override
void dispose() {
// Dispose dependents first.
apiService.dispose();
database.dispose();
super.dispose();
}
}
class RootFactory extends DependencyFactory<RootContainer> {
@override
RootContainer create() => RootContainer(
apiService: LazyGet(ApiService.new, dispose: (api) => api.close()),
database: LazyFutureGet(
DatabaseService.initialize,
dispose: (database) => database.close(),
),
);
}- If nothing was created, the factory is not called and the callback does not run.
- Calling
dispose()twice closes the object once. LazyFutureGetruns the callback asynchronously. If the load is still in progress, the object is closed as soon as it finishes.- The next
instanceaccess afterdispose()creates a new object, which has to be disposed again.
Benefits of Lazy Initialization:
- Faster App Startup: Dependencies are created only when needed
- Memory Optimization: Unused services don't consume memory
- Flexible Initialization: You can control when heavy operations are performed
- Simple API: Easy to use with both sync and async dependencies
example/ is a todo app with nested scopes, lazy dependencies, Clean Architecture layers and widget tests that replace dependencies through a factory.

