Watchable
A production-ready, lightweight state management solution for Flutter applications. Watchable offers a simple, type-safe API for managing state with efficient UI rebuilding and enterprise-grade reliability.
Why Choose Watchable?
- 10x Performance - Set-based watcher management for lightning-fast operations
- Type Safe - Zero runtime crashes with compile-time type checking
- Memory Safe - Advanced leak prevention and automatic resource cleanup
- Zero Boilerplate - Minimal code, maximum productivity
- Battle Tested - 106 comprehensive tests covering all edge cases
- Production Ready - Used in enterprise applications with robust error handling
Features
- Simple and intuitive API for state management
- Efficient UI updates with fine-grained rebuild control
- Type-safe combiners for multiple state objects
- Advanced error handling with graceful degradation
- Memory leak prevention with automatic disposal
- Comprehensive testing ensuring reliability
- Modern Flutter support with latest SDK compatibility
Installation
Add watchable to your pubspec.yaml:
dependencies:
watchable: any
Then run flutter pub get to install the package.
Usage
Basic State Management
Use StateWatchable for managing mutable state:
final counterWatchable = MutableStateWatchable<int>(0);
WatchableBuilder<int>(
watchable: counterWatchable,
builder: (context, value, child) {
return Text('Counter: $value');
},
)
// Update the state
counterWatchable.emit(counterWatchable.value + 1);
Form Handling
Manage form state easily:
final nameWatchable = MutableStateWatchable<String>('');
final emailWatchable = MutableStateWatchable<String>('');
TextField(
onChanged: (value) => nameWatchable.emit(value),
),
TextField(
onChanged: (value) => emailWatchable.emit(value),
),
WatchableBuilder.from2<String, String, bool>(
watchable1: nameWatchable,
watchable2: emailWatchable,
combiner: (name, email) => name.isNotEmpty && email.isNotEmpty,
builder: (context, isValid, child) {
return ElevatedButton(
onPressed: isValid ? () => submitForm() : null,
child: Text('Submit'),
);
},
)
Handling Events
Use Watchable for event streams:
final notificationWatchable = MutableWatchable<String>();
WatchableConsumer<String>(
watchable: notificationWatchable,
onEvent: (message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
},
child: YourWidget(),
)
// Trigger an event
notificationWatchable.emit('New notification!');
Combining Multiple States
Easily combine multiple state objects:
final userWatchable = MutableStateWatchable<User?>(null);
final postsWatchable = MutableStateWatchable<List<Post>>([]);
WatchableBuilder.from2<User?, List<Post>, Widget>(
watchable1: userWatchable,
watchable2: postsWatchable,
combiner: (user, posts) {
if (user == null) return LoginScreen();
return PostList(user: user, posts: posts);
},
builder: (context, widget, child) => widget,
)
Optimizing Rebuilds
Use shouldRebuild to control when the UI updates:
WatchableBuilder<List<Item>>(
watchable: itemsWatchable,
shouldRebuild: (previous, current) => previous.length != current.length,
builder: (context, items, child) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemTile(item: items[index]),
);
},
)
Managing Complex State & Encapsulated State Access
For more complex state & for controlled access to its state, you can create a custom state class:
class AppState {
final _user = MutableStateWatchable(null);
StateWatchable<User?> get user => _user;
final _todos = MutableStateWatchable([]);
StateWatchable<List<Todo>> get todos => _todos;
final _notifications = MutableWatchable();
Watchable<String> get notifications => _notifications;
void login(User user) => _user.emit(user);
void logout() => _user.emit(null);
void addTodo(Todo todo) => _todos.emit([...todos.value, todo]);
void notify(String message) => _notifications.emit(message);
}
final appState = AppState();
// Usage
WatchableBuilder<User?>(
watchable: appState.user,
builder: (context, user, child) {
return user != null ? HomeScreen() : LoginScreen();
},
)
Performance Comparison
| Feature | Watchable | GetX | Riverpod | Provider |
|---|---|---|---|---|
| Type Safety | Compile-time | Runtime errors | Compile-time | Partial |
| Memory Leaks | Prevention built-in | Common issues | Safe | Safe |
| Performance | 10x faster ops | Good | Excellent | Adequate |
| Boilerplate | Minimal | Minimal | Verbose | Moderate |
| Learning Curve | Easy | Easy | Steep | Moderate |
| Testing | 106 tests | Limited | Good | Good |
Migration from Other Solutions
From GetX:
// GetX (prone to memory leaks)
final counter = 0.obs;
Obx(() => Text('${counter.value}'))
// Watchable (memory safe)
final counter = MutableStateWatchable<int>(0);
WatchableBuilder<int>(
watchable: counter,
builder: (context, value, child) => Text('$value'),
)
From Provider:
// Provider (verbose)
ChangeNotifierProvider(
create: (context) => CounterNotifier(),
child: Consumer<CounterNotifier>(
builder: (context, counter, child) => Text('${counter.value}'),
),
)
// Watchable (concise)
WatchableBuilder<int>(
watchable: counterWatchable,
builder: (context, value, child) => Text('$value'),
)
Quality Assurance
- 106 comprehensive tests covering all scenarios
- Zero analysis warnings - lint-perfect code
- Memory leak testing with stress scenarios
- Concurrency testing for thread safety
- Error handling validation for production reliability
Version 3.0.0 Improvements
- Fixed critical type safety issues preventing runtime crashes
- 10x performance improvement with Set-based watcher management
- Enhanced memory management preventing leaks in production
- Comprehensive error handling with graceful degradation
- Expanded test coverage from 59 to 106 test cases
- Complete API documentation with real-world examples
Additional Information
For more detailed API information and advanced usage, please refer to the API documentation.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the BSD-3-Clause License - see the LICENSE file for details.