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.

Libraries

watchable