modular_di 0.1.3 copy "modular_di: ^0.1.3" to clipboard
modular_di: ^0.1.3 copied to clipboard

A simple way to organize dependency injection using modules.

Modular DI (Dependency Injection)


Logo

A simple way to organize dependency injection using modules.
Explore the docs »

Report Bug · Request Feature


Version Pub Points

Pub Publisher



Hey there! 👋 This is a super simple and flexible way to organize your Flutter app into modules, with a dependency injection features thrown in.

Features #

  • 🔌 Works with whatever router you love (Go Router, Auto Route, you name it!)
  • 🎯 Keep your app tidy with a simple module system
  • 💉 Easy dependency injection powered by auto_injector
  • 🔄 Modules can talk to each other through imports
  • 🚀 Modules load up and clean up smoothly
  • 🎨 Drop in the ModuleWidget wherever you need it
  • 🧪 Testing is a breeze with mock replacements
  • 📊 Lightning-fast dependency resolution using directed acyclic graphs

Getting Started #

Add to your pubspec.yaml:

dependencies:
  modular_di: <last version>

Usage #

Creating a Module #

Create a module by extending the Module class:

class UserModule extends Module {
  @override
  List<Type> imports = []; // Add other modules to import if needed

  @override
  FutureOr<void> registerBinds(InjectorRegister i) {
    // Register your dependencies
    i.addSingleton<UserRepository>(() => UserRepositoryImpl());
    i.addSingleton<UserService>(() => UserServiceImpl());
  }
}

Using ModulesManager #

Initialize and manage your modules using ModulesManager:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  //Register and initialize modules
  await ModulesManager.instance.initModules([
    UserModule(),
    AuthModule(),
  ]);
  
  runApp(const MyApp());
}

Registering Modules separately

You can register modules in any order from anywhere, as long as you register them before initializing them.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Register CoreModule
  ModulesManager.instance.registerModule(CoreModule());

  // Register User and Auth modules
  ModulesManager.instance.registerModules([
    UserModule(),
    AuthModule(),
  ]);

  // Initialize all the registered modules
  await ModulesManager.instance.initRegisteredModules();
  
  runApp(const MyApp());
}

Accessing Dependencies #

Use ModuleWidget to provide module access and Module.get<T>() to retrieve dependencies:

class UserScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ModuleWidget<UserModule>(
      child: Builder(
        builder: (context) {
          // Without listening to changes
          final userService = Module.get<UserService>(context);
          return UserContent(service: userService);
        },
      ),
    );
  }
}

Listening to Module Changes

It's recommended to use listen: true when getting dependencies, especially if you're working with modules that might be reset or if you're using imported modules. This ensures your widget rebuilds when dependencies are updated:

class UserProfileWidget extends StatefulWidget {
  @override
  State<UserProfileWidget> createState() => _UserProfileWidgetState();
}

class _UserProfileWidgetState extends State<UserProfileWidget> {
  late UserService _userService;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // With listening to changes - widget will rebuild when:
    // 1. The current module is reset
    // 2. Any imported module is disposed/reset
    _userService = Module.get<UserService>(context, listen: true);
  }

  @override
  Widget build(BuildContext context) {
    return Text(_userService.username);
  }
}

For example, if UserModule imports AuthModule, and you dispose AuthModule using:

await ModulesManager.instance.disposeModule<AuthModule>();

Any widget using listen: true with dependencies from UserModule will automatically rebuild with the new dependencies.

Module Dependencies #

Modules can depend on other modules using the imports property:

class ProfileModule extends Module {
  @override
  List<Type> imports = [UserModule]; // Import dependencies from UserModule

  @override
  FutureOr<void> registerBinds(InjectorRegister i) {
    i.addSingleton<ProfileService>(ProfileServiceImpl.new);
  }
}

Dependency Injection Types #

The package supports different types of dependency injection:

  • Singleton: addSingleton<T>() - Creates a single instance that persists throughout the app
  • Lazy Singleton: addLazySingleton<T>() - Creates a singleton instance only when first requested
  • Factory: add<T>() - Creates a new instance each time it's requested
  • Instance: addInstance<T>() - Registers an existing instance
  • Replace: replace<T>() - Replaces an existing registration (Useful for testing)

Logging #

The package includes a built-in logging system that can be enabled/disabled as needed:

import 'package:modular_di/logger.dart';

// Enable logging (disabled by default)
Logger.enable();

// Disable logging
Logger.disable();

Logs will only be printed in debug mode, making it safe to leave logging code in production.

Want to see it in action? #

Check out our example to see how it all comes together!

Want to help? #

Got ideas? Found a bug? We'd love your help! Feel free to open a PR and join the fun.

This project uses the MIT License - check out LICENSE if you're into that kind of thing.

4
likes
0
points
43
downloads

Publisher

verified publisherdeivao.dev

Weekly Downloads

A simple way to organize dependency injection using modules.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

auto_injector, flutter, meta, uuid

More

Packages that depend on modular_di