watchable 4.0.0
watchable: ^4.0.0 copied to clipboard
Production-ready, type-safe state management for Flutter. 10x performance improvement with advanced memory management and comprehensive error handling.
Watchable #
A production-ready, type-safe state management solution for Flutter applications. Watchable provides a simple extension-based API for managing state with efficient UI rebuilding and enterprise-grade reliability.
Key Features #
- Extension-based API - 70% less boilerplate with
.watchsyntax - Type-safe combiners - Combine 2-6 watchables with compile-time safety
- High performance - 10x faster operations with Set-based management
- Memory safe - Automatic leak prevention and resource cleanup
- Zero configuration - Works out of the box with minimal setup
- Comprehensive testing - 147 test cases covering all scenarios
- Full backward compatibility - Traditional API still supported
Installation #
Add to your pubspec.yaml:
dependencies:
watchable: ^4.0.0
Then run flutter pub get.
Quick Start #
Basic State Management #
import 'package:watchable/watchable.dart';
// Create state with extension syntax
final counter = 0.watch; // WInt
final name = 'John'.watch; // WString
final isLoading = false.watch; // WBool
// Build reactive UI
counter.build((value) => Text('Count: $value'))
// Update state
counter.emit(counter.value + 1);
name.emit('Jane');
isLoading.emit(true);
Form Validation #
final email = ''.watch;
final password = ''.watch;
final confirmPassword = ''.watch;
// Combine multiple states for validation
(email, password, confirmPassword).build((e, p, cp) {
final isValid = e.contains('@') &&
p.length >= 6 &&
p == cp;
return ElevatedButton(
onPressed: isValid ? () => submitForm() : null,
child: Text(isValid ? 'Submit' : 'Fix errors'),
);
})
Event Handling #
final notifications = WEvent<String>();
// Handle events without state persistence
notifications.consume(
onEvent: (message) => ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
),
child: MyWidget(),
);
// Trigger events
notifications.emit('New notification!');
API Reference #
Creating State #
Extension Syntax (Recommended)
// Primitive types
final counter = 0.watch; // WInt
final name = 'John'.watch; // WString
final flag = false.watch; // WBool
final price = 99.99.watch; // WDouble
// Collections
final items = <String>[].watch; // WList<String>
final config = <String, int>{}.watch; // WMap<String, int>
// Custom objects
final user = User().watch; // W<User>
// Event streams (no persistent state)
final events = WEvent<String>();
Direct Constructor (Alternative)
final counter = WInt(0);
final name = WString('John');
final events = WEvent<String>();
Building UI #
Single State
// Extension method (concise)
counter.build((value) => Text('Count: $value'))
// With rebuild control
counter.build(
(value) => Text('Count: $value'),
shouldRebuild: (prev, current) => current % 2 == 0,
)
Multiple States (Combiners)
// 2 states
(firstName, lastName).build((f, l) => Text('$f $l'))
// 3 states
(first, last, age).build((f, l, a) => Text('$f $l ($a)'))
// Up to 6 states
(email, password, confirmPassword, firstName, lastName, agreed).build(
(e, p, cp, fn, ln, a) => /* complex validation logic */
)
Watch Utility Class (Explicit)
// When you prefer explicit method calls
Watch.build2(firstName, lastName, (f, l) => Text('$f $l'))
Watch.build3(first, last, age, (f, l, a) => Text('$f $l ($a)'))
// ... up to Watch.build6()
Combining States #
Create Combined Watchable
// Tuple extension
final fullName = (firstName, lastName).combine((f, l) => '$f $l');
fullName.build((name) => Text(name))
// Watch utility
final userInfo = Watch.combine2(name, age, (n, a) => 'Name: $n, Age: $a');
Common Combiner Patterns
// String operations
final fullName = (firstName, lastName).combine((f, l) => '$f $l');
// Calculations
final total = (price, tax).combine((p, t) => p + (p * t));
// Object creation
final user = (name, email, age).combine((n, e, a) =>
User(name: n, email: e, age: a)
);
// Validation
final isFormValid = (email, password).combine((e, p) =>
e.contains('@') && p.length >= 6
);
// Conditional logic
final status = (isLoggedIn, hasPermission).combine((login, perm) =>
login && perm ? 'Authorized' : 'Unauthorized'
);
Event Handling #
final notifications = WEvent<String>();
// Consumer widget
notifications.consume(
onEvent: (message) => print(message),
child: MyWidget(),
);
// Direct listener
notifications.watch((message) => print(message));
// Emit events
notifications.emit('Hello World');
Advanced Patterns #
Encapsulated State Management
class AppState {
final _user = MutableStateWatchable<User?>(null);
final _todos = MutableStateWatchable<List<Todo>>([]);
final _notifications = MutableWatchable<String>();
// Read-only public accessors
StateWatchable<User?> get user => _user;
StateWatchable<List<Todo>> get todos => _todos;
Watchable<String> get notifications => _notifications;
// Public methods
void login(User user) => _user.emit(user);
void addTodo(Todo todo) => _todos.emit([...todos.value, todo]);
void notify(String message) => _notifications.emit(message);
}
final appState = AppState();
// Usage
appState.user.build((user) => user != null ? HomeScreen() : LoginScreen());
Custom Comparison
final counter = WInt(0, compare: (old, current) => (old - current).abs() < 2);
// Only emits when difference is >= 2
Complex Form Example
class RegistrationForm {
final email = ''.watch;
final password = ''.watch;
final confirmPassword = ''.watch;
final firstName = ''.watch;
final lastName = ''.watch;
final agreedToTerms = false.watch;
Widget build() {
return Column(
children: [
TextField(onChanged: email.emit),
TextField(onChanged: password.emit, obscureText: true),
TextField(onChanged: confirmPassword.emit, obscureText: true),
TextField(onChanged: firstName.emit),
TextField(onChanged: lastName.emit),
CheckboxListTile(
value: agreedToTerms.value,
onChanged: (value) => agreedToTerms.emit(value ?? false),
title: Text('I agree to the terms'),
),
// Combined validation
(email, password, confirmPassword, firstName, lastName, agreedToTerms)
.build((e, p, cp, fn, ln, agreed) {
final isValid = e.contains('@') &&
p.length >= 6 &&
p == cp &&
fn.isNotEmpty &&
ln.isNotEmpty &&
agreed;
return ElevatedButton(
onPressed: isValid ? _submitForm : null,
child: Text(isValid ? 'Create Account' : 'Complete Form'),
);
}),
],
);
}
void _submitForm() {
// Form submission logic
}
}
Performance Comparison #
| Feature | Watchable 4.0 | 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 | 70% reduction | Minimal | Verbose | Moderate |
| Learning Curve | Easy | Easy | Steep | Moderate |
| Testing | 147 comprehensive | Limited | Good | Good |
Migration Guide #
From GetX #
// GetX
final counter = 0.obs;
Obx(() => Text('${counter.value}'))
// Watchable
final counter = 0.watch;
counter.build((value) => Text('$value'))
From Provider #
// Provider
ChangeNotifierProvider(
create: (context) => CounterNotifier(),
child: Consumer<CounterNotifier>(
builder: (context, counter, child) => Text('${counter.value}'),
),
)
// Watchable
final counter = 0.watch;
counter.build((value) => Text('$value'))
From Traditional Watchable API #
// Traditional
final counter = MutableStateWatchable<int>(0);
WatchableBuilder<int>(
watchable: counter,
builder: (context, value, child) => Text('$value'),
)
// Extension API
final counter = 0.watch;
counter.build((value) => Text('$value'))
Supported Combinations #
| Items | Tuple Extension | Watch Utility | Common Use Cases |
|---|---|---|---|
| 2 | (a, b).combine(...) |
Watch.combine2(...) |
Name validation, price calculation |
| 3 | (a, b, c).combine(...) |
Watch.combine3(...) |
Full name, RGB colors, address |
| 4 | (a, b, c, d).combine(...) |
Watch.combine4(...) |
Complete address, RGBA colors |
| 5 | (a, b, c, d, e).combine(...) |
Watch.combine5(...) |
User profile, complex validation |
| 6 | (a, b, c, d, e, f).combine(...) |
Watch.combine6(...) |
Registration forms, detailed config |
For more than 6 items, use the traditional WatchableBuilder.fromList() or chain combinations.
Best Practices #
State Organization #
- Use extension API (
.watch) for new code - Keep state close to where it's used
- Prefer composition over large state objects
- Use read-only accessors for public state
Performance #
- Use
shouldRebuildfor expensive widgets - Combine related states instead of watching separately
- Dispose resources when no longer needed
- Prefer primitive types over complex objects when possible
Testing #
- Test combiner logic separately from UI
- Mock state for widget tests
- Use
dispose()in tearDown methods - Verify state changes with watchers
Version 4.0.0 Highlights #
- Extension-based API for 70% less boilerplate code
- 6-item combiner support for complex state combinations
- Enhanced type safety with compile-time error prevention
- Comprehensive documentation with real-world examples
- 147 test cases ensuring production reliability
- Full backward compatibility with existing code
API Documentation #
For detailed API documentation, visit pub.dev/documentation/watchable.
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.