Instancer
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 typeT
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_runneror 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.
- Check existing issues
- Create a new issue or submit a pull request
- 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!
Libraries
- instancer
- A simple and flexible factory registry for Dart.