initializable 1.0.0
initializable: ^1.0.0 copied to clipboard
Zero-boilerplate async initialization gating for Dart. Ensure your class's async methods automatically suspend until asynchronous setup is complete.
example/initializable_example.dart
// ignore_for_file: unused_local_variable, avoid_print
import 'package:initializable/initializable.dart';
// ============================================================
// Example 1: Non-Throwing Initialization (CacheService)
// ============================================================
/// A cache service that requires async warm-up before use.
///
/// Uses the [Initializable] mixin to gate all data-access methods
/// behind the [warmUp] initialization phase.
class CacheService with Initializable {
late Map<String, String> _store;
/// Loads the cache from disk and opens the initialization gate.
Future<void> warmUp() async {
// Simulate async disk loading
await Future<void>.delayed(const Duration(milliseconds: 100));
_store = {'user:1': 'Alice', 'user:2': 'Bob'};
markInitialized(); // 🔓 Gate opens — all waiting methods proceed
}
/// Returns a cached value by key.
///
/// Automatically waits for [warmUp] to complete before accessing the store.
Future<String?> get(String key) => guarded(() async {
return _store[key];
});
/// Returns all cached keys.
Future<List<String>> keys() => guarded(() async {
return _store.keys.toList();
});
/// Sync method — no gating needed.
String get cacheDir => '/tmp/cache';
}
// ============================================================
// Example 2: Throwing Initialization (DatabaseService)
// ============================================================
/// A database service whose connection can fail.
///
/// Uses [ThrowingInitializable] to propagate connection errors
/// to all methods that depend on the connection.
class DatabaseService with ThrowingInitializable {
String? _connectionId;
/// Attempts to connect to the database.
///
/// On success, opens the gate. On failure, propagates the error
/// to all waiting callers.
Future<void> connect(String url) async {
try {
// Simulate async connection
await Future<void>.delayed(const Duration(milliseconds: 50));
if (url.isEmpty) {
throw ArgumentError('Connection URL cannot be empty');
}
_connectionId = 'conn_${url.hashCode}';
markInitialized(); // ✅ Success
} catch (e, st) {
markFailed(e, st); // ❌ Propagate to all waiters
}
}
/// Executes a query — waits for connection, or throws if it failed.
Future<List<String>> query(String sql) => guarded(() async {
return ['row1: $sql via $_connectionId'];
});
}
// ============================================================
// Main — Demonstrates both patterns
// ============================================================
Future<void> main() async {
// --- Non-throwing example ---
print('=== CacheService (Non-Throwing) ===');
final cache = CacheService();
// Kick off warm-up concurrently
final warmUpFuture = cache.warmUp();
// These calls automatically wait for warmUp() to finish
final user = await cache.get('user:1');
print('user:1 = $user'); // Alice
final allKeys = await cache.keys();
print('keys = $allKeys'); // [user:1, user:2]
print('initialized: ${cache.initialized}'); // true
await warmUpFuture;
// --- Throwing example (success) ---
print('\n=== DatabaseService (Success) ===');
final db = DatabaseService();
await db.connect('postgres://localhost:5432');
final rows = await db.query('SELECT * FROM users');
print('query result: $rows');
print('initialized: ${db.initialized}'); // true
// --- Throwing example (failure) ---
print('\n=== DatabaseService (Failure) ===');
final failingDb = DatabaseService();
await failingDb.connect(''); // Empty URL → failure
try {
await failingDb.query('SELECT 1');
} catch (e) {
print('query threw: $e'); // ArgumentError
}
print('initialized: ${failingDb.initialized}'); // false
}