Skip to content

A simple, lightweight factory registry for Dart that makes creating instances of registered types easy and flexible.

License

Notifications You must be signed in to change notification settings

uproid/instancer

Instancer

pub package Donate issues-closed issues-open Contributions

A simple, lightweight factory registry for Dart that makes creating instances of registered types easy and flexible.

Perfect for dependency injection, prototyping, configuration management, and testing!

Features

Simple API: Just 5 intuitive methods to learn
🎯 Type-safe: Full generic type support
🪶 Lightweight: Zero dependencies
🔧 Flexible: Register any factory function
🧪 Testable: Easy to clear and reset for testing

Getting started

Add this to your pubspec.yaml:

dependencies:
  instancer: ^1.0.0

Then import it:

import 'package:instancer/instancer.dart';

Usage

🚀 Quick Start

import 'package:instancer/instancer.dart';

class User {
  final String name;
  final int age;
  
  User({required this.name, required this.age});
}

void main() {
  // 1️⃣ Register a factory function
  Instancer.register<User>(() => User(name: 'Guest', age: 18));
  
  // 2️⃣ Create instances anywhere in your code
  final user1 = Instancer.create<User>();
  final user2 = Instancer.create<User>();
  
  print(user1.name); // Output: Guest
  print(identical(user1, user2)); // Output: false (different instances)
}

📦 Real-World Examples

Example 1: Configuration Management

class AppConfig {
  final String apiUrl;
  final bool debugMode;
  final int timeout;
  
  AppConfig({
    required this.apiUrl, 
    required this.debugMode,
    required this.timeout,
  });
}

void main() {
  // Development environment
  Instancer.register<AppConfig>(
    () => AppConfig(
      apiUrl: 'http://localhost:3000',
      debugMode: true,
      timeout: 30,
    ),
  );
  
  // Use it anywhere in your app
  final config = Instancer.create<AppConfig>();
  print(config.apiUrl); // http://localhost:3000
  
  // Switch to production (just re-register)
  Instancer.register<AppConfig>(
    () => AppConfig(
      apiUrl: 'https://api.production.com',
      debugMode: false,
      timeout: 10,
    ),
  );
  
  final prodConfig = Instancer.create<AppConfig>();
  print(prodConfig.apiUrl); // https://api.production.com
}

Example 2: Dependency Injection

class Database {
  void query(String sql) => print('Executing: $sql');
}

class UserRepository {
  final Database database;
  
  UserRepository({required this.database});
  
  void save(String name) {
    database.query("INSERT INTO users (name) VALUES ('$name')");
  }
}

class UserService {
  final UserRepository repository;
  
  UserService({required this.repository});
  
  void createUser(String name) {
    print('Creating user: $name');
    repository.save(name);
  }
}

void main() {
  // Register dependencies in order
  Instancer.register<Database>(() => Database());
  
  Instancer.register<UserRepository>(
    () => UserRepository(database: Instancer.create<Database>()),
  );
  
  Instancer.register<UserService>(
    () => UserService(repository: Instancer.create<UserRepository>()),
  );
  
  // Use the service
  final userService = Instancer.create<UserService>();
  userService.createUser('Alice');
  // Output:
  // Creating user: Alice
  // Executing: INSERT INTO users (name) VALUES ('Alice')
}

Example 3: Testing with Mocks

abstract class ApiClient {
  Future<String> fetchData();
}

class RealApiClient implements ApiClient {
  @override
  Future<String> fetchData() async {
    // Real network call
    return 'Real data from server';
  }
}

class MockApiClient implements ApiClient {
  @override
  Future<String> fetchData() async {
    // Mock data for testing
    return 'Mock data';
  }
}

void main() {
  // Production code
  Instancer.register<ApiClient>(() => RealApiClient());
  
  // In your tests, just re-register with mock
  Instancer.register<ApiClient>(() => MockApiClient());
  
  final client = Instancer.create<ApiClient>();
  client.fetchData().then(print); // Output: Mock data
  
  // Clean up after tests
  Instancer.clear();
}

Example 4: Prototype Pattern

class EmailTemplate {
  final String subject;
  final String body;
  final List<String> recipients;
  
  EmailTemplate({
    required this.subject,
    required this.body,
    this.recipients = const [],
  });
  
  @override
  String toString() => 'Email: $subject to ${recipients.length} recipients';
}

void main() {
  // Register a prototype template
  Instancer.register<EmailTemplate>(
    () => EmailTemplate(
      subject: 'Welcome!',
      body: 'Welcome to our service',
      recipients: ['user@example.com'],
    ),
  );
  
  // Create multiple emails from the same template
  final email1 = Instancer.create<EmailTemplate>();
  final email2 = Instancer.create<EmailTemplate>();
  
  print(email1); // Email: Welcome! to 1 recipients
  print(email2); // Email: Welcome! to 1 recipients
  print('Same instance? ${identical(email1, email2)}'); // false
}

Example 5: Factory with State

class Logger {
  static int _instanceCount = 0;
  final int id;
  
  Logger() : id = ++_instanceCount;
  
  void log(String message) {
    print('[Logger $id] $message');
  }
}

void main() {
  // Each creation increases the counter
  Instancer.register<Logger>(() => Logger());
  
  final logger1 = Instancer.create<Logger>();
  final logger2 = Instancer.create<Logger>();
  final logger3 = Instancer.create<Logger>();
  
  logger1.log('First message');   // [Logger 1] First message
  logger2.log('Second message');  // [Logger 2] Second message
  logger3.log('Third message');   // [Logger 3] Third message
}

API Reference

register<T>(T Function() factory)

Registers a factory function for type T.

Instancer.register<User>(() => User(name: 'John', age: 25));

Parameters:

  • factory: A function that returns a new instance of type T

create<T>()

Creates a new instance of type T using the registered factory.

final user = Instancer.create<User>();

Returns: A new instance of type T

Throws: StateError if no factory is registered for type T

isRegistered<T>()

Checks if a factory is registered for type T.

if (Instancer.isRegistered<User>()) {
  print('User factory is registered!');
}

Returns: true if registered, false otherwise

unregister<T>()

Removes the factory for type T.

final removed = Instancer.unregister<User>();
print('Factory removed: $removed');

Returns: true if a factory was removed, false if none existed

clear()

Removes all registered factories. Useful for testing or resetting state.

Instancer.clear();

count

Returns the number of registered factories.

print('Total registered factories: ${Instancer.count}');

Why Instancer?

Feature Instancer Traditional Singleton Factory Pattern
No inheritance required
No code generation
Multiple instances
Easy to test
Dynamic registration
Zero dependencies

Benefits:

  • No inheritance required: Your classes don't need to extend anything
  • No code generation: Works without build_runner or code generation
  • Pure Dart: No platform-specific code, works everywhere (Flutter, CLI, Web)
  • Flexible: Register simple constructors or complex initialization logic
  • Testable: Easy to swap implementations for mocking in tests
  • Simple: Only 5 methods to learn - register, create, isRegistered, unregister, clear

Common Use Cases

Dependency Injection - Register and inject dependencies
Configuration Management - Switch between dev/prod configs
Testing - Replace real implementations with mocks
Prototype Pattern - Create multiple instances from templates
Factory Pattern - Centralized instance creation
Plugin Systems - Register and create plugin instances

Tips & Best Practices

1. Register early, use anywhere

// In your app initialization
void setupDependencies() {
  Instancer.register<Database>(() => SqliteDatabase());
  Instancer.register<ApiClient>(() => HttpApiClient());
  Instancer.register<AuthService>(() => AuthService());
}

// Use anywhere in your app
final auth = Instancer.create<AuthService>();

2. Use for environment-specific configurations

void setupConfig(Environment env) {
  if (env == Environment.dev) {
    Instancer.register<Config>(() => DevConfig());
  } else {
    Instancer.register<Config>(() => ProdConfig());
  }
}

3. Clean up in tests

void main() {
  setUp(() {
    Instancer.register<Service>(() => MockService());
  });
  
  tearDown(() {
    Instancer.clear(); // Always clean up!
  });
  
  test('service works', () {
    final service = Instancer.create<Service>();
    // Test...
  });
}

Contributing

Contributions are welcome! Please read our CONTRIBUTING.md for details.

  1. Check existing issues
  2. Create a new issue or submit a pull request
  3. Follow the existing code style

License

MIT License - see the LICENSE file for details.

Author

Created with ❤️ by Uproid


Like this package? Give it a ⭐ on GitHub!

About

A simple, lightweight factory registry for Dart that makes creating instances of registered types easy and flexible.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

  •  

Packages

No packages published

Languages