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.

Stop scattering if (!isReady) return; checks everywhere. Gate your async methods behind initialization — automatically.
Initializable ensures that your class's async methods automatically suspend until asynchronous setup is complete. No more runtime crashes from uninitialized state. Works with any Dart class.
🛑 The Problem #
Classes often require asynchronous setup before they're ready — connecting to a database, loading configuration, or authenticating with a remote server. Every method that depends on this setup must somehow wait until it's done.
The Tedious Way (Without Initializable) #
class DatabaseService {
bool _isReady = false;
Future<List<Row>> query(String sql) async {
// 😩 You have to remember this everywhere
while (!_isReady) {
await Future.delayed(Duration(milliseconds: 10));
}
return await db.execute(sql);
}
Future<void> insert(Row row) async {
// 😩 Miss one and you get a runtime crash
while (!_isReady) {
await Future.delayed(Duration(milliseconds: 10));
}
await db.insert(row);
}
}
This is tedious, error-prone, and doesn't scale.
✨ The Solution #
Initializable gives you a guarded() wrapper. Every guarded method automatically waits for initialization to complete!
The Elegant Way (With Initializable) #
class DatabaseService with Initializable {
Future<void> setup() async {
await connectToDatabase();
markInitialized(); // 🔓 Gate opens — all waiting methods proceed!
}
// ✅ Automatically waits for setup() — ZERO boilerplate
Future<List<Row>> query(String sql) => guarded(() async {
return await db.execute(sql);
});
Future<void> insert(Row row) => guarded(() async {
await db.insert(row);
});
}
Zero runtime overhead after initialization. Zero chance of forgetting a check.
📖 Table of Contents #
🚀 Quick Start #
1. Add the Package #
dependencies:
initializable: ^1.0.0
2. Mix In & Use #
import 'package:initializable/initializable.dart';
class MyService with Initializable {
Future<void> setup() async {
await performAsyncWork();
markInitialized(); // 🔓 Done!
}
Future<Data> fetchData() => guarded(() async {
return await loadData(); // ✨ Automatically waits for setup()
});
}
3. Call It #
final service = MyService();
// Kick off setup concurrently
service.setup();
// This will safely wait until setup() completes!
final data = await service.fetchData();
🧠 Core Concepts #
Initializable uses a gate pattern: an internal Completer<void> suspends async callers until markInitialized() is called.
| Concept | Description |
|---|---|
🧬 Mixin (Initializable) |
Provides markInitialized(), guarded(), awaitInitialized(), and initialized. |
🚧 Gate (InitializationGate) |
Holds the Completer and manages state transitions. |
🛡️ Guarded (guarded()) |
Wraps an async action to wait for initialization first. |
🧬 Throwing Mixin (ThrowingInitializable) |
Adds markFailed() for failable initialization. |
🚧 Throwing Gate (ThrowingInitializationGate) |
Supports success/failure with error propagation. |
Variants #
| Component | Non-Throwing | Throwing (Failable) |
|---|---|---|
| Mixin | Initializable |
ThrowingInitializable |
| Gate | InitializationGate |
ThrowingInitializationGate |
📚 Usage Guide #
1. Non-Throwing Initialization #
Use this when your setup cannot fail (e.g., loading a local cache).
import 'package:initializable/initializable.dart';
class CacheService with Initializable {
late Map<String, Data> _store;
Future<void> warmUp() async {
_store = await loadFromDisk();
markInitialized(); // 🔓 Gate opens
}
// ✅ Guarded — waits for warmUp()
Future<Data?> get(String key) => guarded(() async {
return _store[key];
});
// ❌ Sync — no gating needed
String get cacheDir => '/tmp/cache';
}
What Happens at Runtime
Caller 1 → guarded(get("key")) → Gate: pending → SUSPEND
Caller 2 → guarded(get("key")) → Gate: pending → SUSPEND
markInitialized() → Gate: initialized
Caller 1 ← resume ✅ → returns _store["key"]
Caller 2 ← resume ✅ → returns _store["key"]
Future calls → Gate: initialized → proceed immediately (zero overhead)
2. Throwing Initialization (Failable) #
Use this when your setup can fail (e.g., network connections, database migrations).
import 'package:initializable/initializable.dart';
class DatabaseService with ThrowingInitializable {
late DbConnection _connection;
Future<void> connect(String url) async {
try {
_connection = await DbConnection.open(url);
markInitialized(); // ✅ Success
} catch (e, st) {
markFailed(e, st); // ❌ Propagate error to all waiters
}
}
// ✅ Waits for connect(), or throws if it failed
Future<List<Row>> query(String sql) => guarded(() async {
return await _connection.execute(sql);
});
}
State Machine
┌──────────────────┐
│ Pending │
└────────┬─────────┘
│
┌───────────┴───────────┐
│ │
markInitialized() markFailed(error)
│ │
▼ ▼
┌──────────────┐ ┌───────────────┐
│ Initialized │ │ Failed │
└──────────────┘ └───────────────┘
guarded() → runs guarded() → throws
immediately stored error
State Stickiness: The first call to
markInitialized()ormarkFailed()wins. Subsequent calls to either method are safe no-ops.
3. Manual Per-Method Control #
If you prefer fine-grained control, use awaitInitialized() directly instead of guarded():
class SelectiveService with Initializable {
Future<void> setup() async {
await loadResources();
markInitialized();
}
// Manual gating — only this method waits
Future<Result> criticalOperation() async {
await awaitInitialized();
return performWork();
}
// No gating — caller responsible for timing
Future<Result?> bestEffortOperation() async {
return tryPerformWork();
}
}
📖 API Reference #
Mixins #
Initializable
| Member | Type | Description |
|---|---|---|
initialized |
bool (getter) |
Whether markInitialized() has been called. |
markInitialized() |
void |
Opens the gate. Idempotent. |
guarded<T>(action) |
Future<T> |
Executes action after initialization. |
awaitInitialized() |
Future<void> |
Raw suspension point. Prefer guarded(). |
ThrowingInitializable
| Member | Type | Description |
|---|---|---|
initialized |
bool (getter) |
true only on success. |
isResolved |
bool (getter) |
true after success or failure. |
markInitialized() |
void |
Opens the gate. Idempotent + sticky. |
markFailed(error, [st]) |
void |
Fails the gate. Idempotent + sticky. |
guarded<T>(action) |
Future<T> |
Executes action or throws. |
awaitInitialized() |
Future<void> |
Raw suspension. Throws on failure. |
Gates #
InitializationGate
| Member | Description |
|---|---|
initialized |
Whether the gate is open. |
markInitialized() |
Opens the gate. Idempotent. |
wait() |
Suspends until the gate opens. |
ThrowingInitializationGate
| Member | Description |
|---|---|
initialized |
true only on success. |
isResolved |
true after success or failure. |
markInitialized() |
Opens the gate. Idempotent + sticky. |
markFailed(error, [st]) |
Fails the gate. Idempotent + sticky. |
wait() |
Suspends until resolved; throws on failure. |
🏗 Architecture #
lib/
├── initializable.dart # Barrel export
└── src/
├── initialization_gate.dart # InitializationGate (non-throwing)
├── throwing_initialization_gate.dart # ThrowingInitializationGate
├── initializable_mixin.dart # Initializable mixin
└── throwing_initializable_mixin.dart # ThrowingInitializable mixin
Design Principles #
- Zero dependencies — Only depends on
dart:async(core SDK). - No code generation — No
build_runner, no.g.dartfiles. - Single-threaded safety — Dart's event loop guarantees sequential access within an isolate.
- Completer-based gating — Multiple
Futurelisteners on a singleCompleter.futureall resume together.
Ported from Swift #
This package is a Dart port of the Swift Initializable package, which uses Swift Macros for compile-time code injection. Since Dart lacks macros, the guarded() wrapper pattern provides equivalent runtime behavior.
📦 Installation #
Add to your pubspec.yaml:
dependencies:
initializable: ^1.0.0
Then run:
dart pub get
⚙️ Requirements #
| Tool | Minimum Version |
|---|---|
| Dart SDK | 3.12.2 |
📄 License #
This project is available under the MIT License. See the LICENSE file for details.
Built with ❤️ for the Dart ecosystem