injectable

repository·master·Indexed 20 days ago

https://github.com/milad-akarie/injectable

A code generation library for Dart and Flutter that simplifies dependency injection by automating the registration of factories, singletons, and lazy singletons on top of GetIt. It supports features such as asynchronous dependencies, third-party module registration via @module, environment-specific dependencies, and custom creation logic using @factoryMethod.

Tokens
8.8K
Snippets
39
Records
41
Agent score
69%

What's inside injectable

  1. How to use GetIt scopes with injectable

    master

    You can use GetIt scopes to group related dependencies that should only be initialized and disposed of when needed.

    To register a dependency within a specific scope, annotate it with @Scope('scope-name') or use the scope property in @Injectable.

    Dependencies tagged with a scope will be generated inside a separate initialization method/extension specific to that scope. Because scope-init methods may return a Future if they have pre-resolved dependencies, you should await them.

    To use a scope, call the generated extension or method on your GetIt instance.

    // 1. Annotate the dependency with a scope
    @Injectable(scope: 'auth')
    class AuthController {}
    
    // 2. Initialize the scope when needed
    // Using extensions
    await getIt.initAuthScope();
    
    // OR using methods
    await initAuthScope(getIt);
  2. Enable and use Accessor Methods

    master

    By default, dependencies are accessed via getIt.get<Type>(). Enabling generateAccessors: true in @InjectableInit creates type-safe, camelCased getter methods on the GetIt extension.

    • Simple types: UserService becomes getIt.userService.
    • Parameterized dependencies: If a class requires a @factoryParam, the accessor will include that parameter as a named argument.
    // Configuration
    @InjectableInit(generateAccessors: true)
    void configureDependencies() => getIt.init();
    
    // Usage with simple types
    var userService = getIt.userService;
    
    // Usage with parameterized dependencies
    // If UserRepository has: UserRepository(@factoryParam String userId);
    var repository = getIt.userRepository(userId: 'user123');
  3. Set up Injectable in your project

    master

    Follow these steps to initialize dependency injection:

    1. Create a Dart file with a global GetIt instance.
    2. Define a top-level configuration function annotated with @InjectableInit.
    3. Import the generated .config.dart file.
    4. Call the generated .init() extension method inside your configuration function.
    5. Call your configuration function in main() before runApp().

    You can use the generateForDir property in @InjectableInit to restrict generation to specific directories (e.g., ['test']).

    import '<FILE_NAME>.config.dart';
    
    final getIt = GetIt.instance;  
      
    @InjectableInit(
      initializerName: 'init', // default  
      preferRelativeImports: true, // default  
      asExtension: true, // default  
      generateAccessors: false, // default  
    )  
    void configureDependencies() => getIt.init();  
    
    void main() {
      configureDependencies(); 
      runApp(MyApp()); 
    }
  4. Register dependencies for specific environments

    master

    Use the @Environment(String name) annotation to register dependencies only when a specific environment is active.

    Usage:

    1. Annotate a class with @Environment('dev').
    2. When calling the generated init function, pass the environment name: await getIt.init(environment: 'dev');.

    Custom Environments: You can create global constants for environments to avoid string typos:

    const dev = Environment('dev');
    
    @dev
    @injectable
    class ServiceA {}

    Environment Filtering: When calling init, you can provide an EnvironmentFilter. Shipped filters include:

    • NoEnvOrContainsAll
    • NoEnvOrContainsAny
    • SimpleEnvironmentFilter
    @Environment("dev")
    @injectable
    class ServiceA {}
  5. Install Injectable and its dependencies

    master

    To use Injectable, add injectable and get_it to your dependencies, and injectable_generator and build_runner to your dev_dependencies in pubspec.yaml.

    dependencies:
      # add injectable to your dependencies  
      injectable:
      # add get_it  
      get_it:
    
    dev_dependencies:
      # add the generator to your dev_dependencies  
      injectable_generator:
      # add build runner if not already added  
      build_runner:  
  6. Register third-party dependencies using @module

    master

    To register classes you do not own (third-party types), create an abstract class annotated with @module. You can then expose these types as property accessors or methods.

    • Standard registration: Use @singleton, @lazySingleton, etc.
    • Async third-party types: Wrap the return value in a Future.
    • Pre-resolving third-party types: Use @preResolve to ensure the value is awaited before registration.
    • Custom initializers: If a third-party type requires complex setup, define it as a method in the module to allow injectable to inject its parameters.
    @module
    abstract class RegisterModule {
      @singleton
      ThirdPartyType get thirdPartyType;
    
      @preResolve
      Future<SharedPreferences> get prefs => SharedPreferences.getInstance();
    }
  7. Register asynchronous dependencies

    master

    To register a dependency that requires asynchronous initialization, use a static method that returns a Future. When you annotate this method with @factoryMethod, injectable will automatically register it as an asynchronous factory in GetIt.

    Note: Requires GetIt >= 4.0.0. When resolving these dependencies, use getAsync<T>() instead of get<T>().

    Pre-resolving async dependencies: If you want to await the future and register the resolved value directly (making the init function of the generated code async), use the @preResolve annotation on the @factoryMethod or @PostConstruct method.

    @injectable
    class ApiClient {
      @factoryMethod
      static Future<ApiClient> create(Deps ...) async {
        // ... async logic
        return apiClient;
      }
    }
  8. Use LeanBuilder for fast incremental builds (Experimental)

    master

    For super-fast incremental builds, you can use lean_builder instead of build_runner.

    1. Add lean_builder to your dev_dependencies.
    2. Run the builder using dart run lean_builder build (one-time) or dart run lean_builder watch (watch mode).

    To prevent injectable_generator from running via build_runner when using lean_builder, disable it in your build.yaml file.

    dev_dependencies:
      injectable_generator: <latest-version>
      lean_builder: <latest-version>
    # For one-time build:
    dart run lean_builder build
    
    # For watching files and rebuilding on changes:
    dart run lean_builder watch
    targets:
      $default:
        builders:
          injectable_generator:injectable_builder:
            enabled: false
          injectable_generator:injectable_config_builder:
            enabled: false
  9. Bind abstract classes to implementations

    master

    To register a concrete implementation under an abstract type, use the as property within the @Injectable, @Singleton, or @LazySingleton annotations.

    Multiple implementations: Since you cannot bind multiple implementations to the same type directly, use @Named(String name) to tag implementations. When injecting, use @Named(String name) in the constructor to specify which implementation you want.

    Auto Tagging:

    • Use @named (lowercase) to automatically assign the implementation class name as the instance name.
    • Use @Named.from(Type) to retrieve the instance by its implementation type name.
    // Binding to an abstract type
    @Injectable(as: Service)
    class ServiceImpl implements Service {}
    
    // Using tags for multiple implementations
    @Named("impl1")
    @Injectable(as: Service)
    class ServiceImpl1 implements Service {}
    
    @injectable
    class MyRepo {
      final Service service;
      MyRepo(@Named('impl1') this.service);
    }
    
    // Auto Tagging
    @named
    @Injectable(as: Service)
    class ServiceImpl1 implements Service {}
    
    @injectable
    class MyRepo {
      final Service service;
      MyRepo(@Named.from(ServiceImpl1) this.service);
    }
  10. Allow multiple registrations of the same type

    master

    By default, registering multiple implementations of the same type causes an error. To allow this, set allowMultipleRegistrations: true in @InjectableInit.

    Once enabled, you can retrieve all registered instances using getIt.getAll<Type>(). Note that getIt.get<Type>() will still only return the first registered instance.

    @InjectableInit(
      allowMultipleRegistrations: true,
    )
    void configureDependencies() => getIt.init();
    
    @Injectable(as: Plugin) 
    class PluginA implements Plugin {}
    
    @Injectable(as: Plugin) 
    class PluginB implements Plugin {}
    
    // Usage
    var allPlugins = getIt.getAll<Plugin>();
  11. Configure auto-registration via build.yaml

    master

    Instead of annotating every class, you can use Convention Based Configuration to automatically register classes that match specific naming patterns. This is configured in a build.yaml file in your project root.

    Supported patterns:

    • class_name_pattern: Regex for class names (e.g., Service$).
    • file_name_pattern: Regex for filenames (e.g., _service$).
    targets:
      $default:
        builders:
          injectable_generator:injectable_builder:
            options:
              auto_register: true
              class_name_pattern:
                "Service$|Repository$|Bloc$"
              file_name_pattern:
                "_service$|_repository$|_bloc$"
  12. Configure MicroPackages and external modules

    master

    MicroPackages

    MicroPackages are sub-packages that are automatically initialized by the root package. To make a package a MicroPackage, annotate its initialization function with @InjectableInit.microPackage().

    External Modules

    You can manually include external modules using the externalPackageModules properties in @InjectableInit.

    • externalPackageModulesBefore: Modules are initialized before the root dependencies.
    • externalPackageModulesAfter: Modules are initialized after the root dependencies.
    • includeMicroPackages: Set to false in @InjectableInit to disable automatic inclusion of MicroPackages.

    Scoped External Modules

    You can also assign a scope to an ExternalModule to ensure it is initialized within a specific scope rather than the main scope.

    // Defining a MicroPackage
    @InjectableInit.microPackage()
    void initMicroPackage() {}
    
    // Configuring external modules in the root package
    @InjectableInit(
      includeMicroPackages: false, // Disable auto-inclusion
      externalPackageModulesBefore: [
        ExternalModule(AwesomePackageModule, scope: 'awesome'),
      ],
      externalPackageModulesAfter: [
        ExternalModule(CoolPackageModule),
      ],
    )
    void configureDependencies() {}
    
    // Initializing a scoped external module
    await getIt.initAwesomeScope();