Skip to main content

depend 5.2.0 copy "depend: ^5.2.0" to clipboard
depend: ^5.2.0 copied to clipboard

depend simplifies dependency management in Flutter apps, providing easy initialization and access to services across the widget tree.

Logo Frame

depend is a library for dependency management in Flutter applications. It provides a convenient way to initialize and access services and repositories via InheritedWidget.


Features 🚀 #

  • 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.

Table of Contents #


Installation #

Add the library to the pubspec.yaml of your project:

dependencies:
  depend: ^latest_version

Install the dependencies:

flutter pub get

Usage Examples #

Example 1: Simple Initialization #

Step 1: Define the Dependency

Create a class that extends DependencyContainer and initialize your dependencies:

class RootContainer extends DependencyContainer {
  final ApiService apiService;

  RootContainer({required this.apiService});


  void dispose() {
    apiService.dispose();
  }
}

Step 2: Define the DependencyFactory

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(),
     );
  }
}

Step 3: Use DependencyScope

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(),
    ),
  );
}

Step 4: Access the Dependency in a Widget

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}');
      },
    );
  }
}

Example 2: DependencyProvider #

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);
    ...
  }
}

Example 3: DependencyScope #

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


Example 4: Lazy Initialization #

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.

Using LazyGet for Synchronous Dependencies

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.

Using LazyFutureGet for Asynchronous Dependencies

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.

Disposing Lazy Dependencies

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.
  • LazyFutureGet runs the callback asynchronously. If the load is still in progress, the object is closed as soon as it finishes.
  • The next instance access after dispose() 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 App #

example/ is a todo app with nested scopes, lazy dependencies, Clean Architecture layers and widget tests that replace dependencies through a factory.

Codecov #

Codecov

10
likes
160
points
1.07k
downloads
screenshot

Documentation

API reference

Publisher

verified publisherbangertstudio.kz

Weekly Downloads

depend simplifies dependency management in Flutter apps, providing easy initialization and access to services across the widget tree.

Repository (GitHub)
View/report issues

Topics

#dependency #injection #dependency-injection #service #dependency-management

License

BSD-3-Clause (license)

Dependencies

flutter

More

Packages that depend on depend