watchable 5.0.0 copy "watchable: ^5.0.0" to clipboard
watchable: ^5.0.0 copied to clipboard

Production-ready, type-safe state management for Flutter. 10x performance improvement with advanced memory management and comprehensive error handling.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:watchable/watchable.dart';

void main() {
  runApp(const MaterialApp(
    home: MainPage(),
  ));
}

class MainPage extends StatefulWidget {
  // =======================================================================
  // NEW CONST-COMPATIBLE WATCHABLE API
  // =======================================================================

  // Basic const watchables - can be created as const!
  static const counter = Watchable(0);
  static const name = Watchable('John');
  static const isLoading = Watchable(false);
  static const price = Watchable(99.99);
  static const items = Watchable<List<String>>([]);

  const MainPage({super.key});

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  // =======================================================================
  var extensionCounter = 42.watchable;

  var extensionName = 'Jane'.watchable;

  var extensionPrice = 149.99.watchable;

  var extensionItems = <String>['apple', 'banana'].watchable;

  var extensionFlags = <String, bool>{'dark_mode': false}.watchable;

  // Non-const watchables for different initial values
  final email = const Watchable('');

  final password = const Watchable('');

  final status = const Watchable('Ready');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Const-Compatible Watchable Demo'),
        backgroundColor: Colors.blue.shade600,
        foregroundColor: Colors.white,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildHeader('✨ NEW CONST-COMPATIBLE WATCHABLE API'),
            const SizedBox(height: 16),
            _buildSection(
              'Basic Counter Example',
              'Direct value modification with const watchables',
              [
                WatchableBuilder<int>(
                  watchable: MainPage.counter,
                  builder: (value) => Text(
                    'Counter: $value',
                    style: const TextStyle(fontSize: 24, color: Colors.blue),
                  ),
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    ElevatedButton(
                      onPressed: () => MainPage.counter.value++,
                      child: const Text('Increment'),
                    ),
                    const SizedBox(width: 8),
                    ElevatedButton(
                      onPressed: () => MainPage.counter.value--,
                      child: const Text('Decrement'),
                    ),
                    const SizedBox(width: 8),
                    ElevatedButton(
                      onPressed: () => MainPage.counter.value = 0,
                      child: const Text('Reset'),
                    ),
                  ],
                ),
              ],
            ),
            _buildSection(
              'Name Input Example',
              'String watchable with real-time updates',
              [
                WatchableBuilder<String>(
                  watchable: MainPage.name,
                  builder: (value) => Text(
                    'Hello, $value!',
                    style: const TextStyle(fontSize: 20, color: Colors.green),
                  ),
                ),
                const SizedBox(height: 8),
                TextField(
                  onChanged: (value) => MainPage.name.value = value,
                  decoration: const InputDecoration(
                    labelText: 'Enter your name',
                    border: OutlineInputBorder(),
                  ),
                ),
              ],
            ),
            _buildSection(
              'Loading State Example',
              'Boolean watchable for state management',
              [
                WatchableBuilder<bool>(
                  watchable: MainPage.isLoading,
                  builder: (loading) => loading
                      ? const Column(
                          children: [
                            CircularProgressIndicator(),
                            SizedBox(height: 8),
                            Text('Loading...'),
                          ],
                        )
                      : const Text(
                          '✅ Ready!',
                          style: TextStyle(fontSize: 18, color: Colors.green),
                        ),
                ),
                const SizedBox(height: 8),
                ElevatedButton(
                  onPressed: () =>
                      MainPage.isLoading.value = !MainPage.isLoading.value,
                  child: const Text('Toggle Loading'),
                ),
              ],
            ),
            _buildSection(
              'Price Calculator',
              'Double watchable with formatting',
              [
                WatchableBuilder<double>(
                  watchable: MainPage.price,
                  builder: (value) => Text(
                    'Price: \$${value.toStringAsFixed(2)}',
                    style: const TextStyle(fontSize: 18, color: Colors.orange),
                  ),
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    ElevatedButton(
                      onPressed: () => MainPage.price.value += 10,
                      child: const Text('+\$10'),
                    ),
                    const SizedBox(width: 8),
                    ElevatedButton(
                      onPressed: () => MainPage.price.value -= 10,
                      child: const Text('-\$10'),
                    ),
                    const SizedBox(width: 8),
                    ElevatedButton(
                      onPressed: () => MainPage.price.value = 99.99,
                      child: const Text('Reset'),
                    ),
                  ],
                ),
              ],
            ),
            _buildHeader('🔄 TRANSFORMATION FUNCTIONS'),
            _buildSection(
              'Map Transformation',
              'Transform values with map() function',
              [
                WatchableBuilder<String>(
                  watchable: MainPage.counter.map((value) =>
                      'Count: $value (${value.isEven ? 'Even' : 'Odd'})'),
                  builder: (value) => Text(
                    value,
                    style: const TextStyle(fontSize: 16, color: Colors.purple),
                  ),
                ),
              ],
            ),
            _buildSection(
              'Where Filter',
              'Filter values with where() function',
              [
                WatchableBuilder<int>(
                  watchable: MainPage.counter.where((value) => value >= 0),
                  builder: (value) => Text(
                    'Positive Counter: $value',
                    style: const TextStyle(fontSize: 16, color: Colors.teal),
                  ),
                ),
              ],
            ),
            _buildSection(
              'Distinct Values',
              'Remove duplicates with distinct() function',
              [
                WatchableBuilder<int>(
                  watchable: MainPage.counter.distinct(),
                  builder: (value) => Text(
                    'Distinct Counter: $value',
                    style: const TextStyle(fontSize: 16, color: Colors.indigo),
                  ),
                ),
              ],
            ),
            _buildHeader('🎯 .watchable EXTENSION EXAMPLES'),
            _buildSection(
              'Extension Counter Example',
              'Using .watchable syntax with specialized methods',
              [
                WatchableBuilder<int>(
                  watchable: extensionCounter,
                  builder: (value) => Text(
                    'Extension Counter: $value',
                    style: const TextStyle(fontSize: 24, color: Colors.purple),
                  ),
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    ElevatedButton(
                      onPressed: () => extensionCounter.increment(),
                      child: const Text('++'),
                    ),
                    const SizedBox(width: 8),
                    ElevatedButton(
                      onPressed: () => extensionCounter.decrement(),
                      child: const Text('--'),
                    ),
                    const SizedBox(width: 8),
                    ElevatedButton(
                      onPressed: () => extensionCounter.value = 42,
                      child: const Text('Reset to 42'),
                    ),
                  ],
                ),
              ],
            ),
            _buildSection(
              'Extension Collections',
              'List and Map extensions with specialized methods',
              [
                WatchableBuilder<List<String>>(
                  watchable: extensionItems,
                  builder: (items) => Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Items (${items.length}): ${items.join(', ')}',
                        style:
                            const TextStyle(fontSize: 16, color: Colors.indigo),
                      ),
                      const SizedBox(height: 8),
                      Row(
                        children: [
                          ElevatedButton(
                            onPressed: () => extensionItems.add('orange'),
                            child: const Text('Add Orange'),
                          ),
                          const SizedBox(width: 8),
                          ElevatedButton(
                            onPressed: () => extensionItems.remove('apple'),
                            child: const Text('Remove Apple'),
                          ),
                          const SizedBox(width: 8),
                          ElevatedButton(
                            onPressed: () => extensionItems.clear(),
                            child: const Text('Clear'),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ],
            ),
            _buildSection(
              'Extension Map Example',
              'Map watchable with toggle functionality',
              [
                WatchableBuilder<Map<String, bool>>(
                  watchable: extensionFlags,
                  builder: (flags) => Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Flags: ${flags.entries.map((e) => '${e.key}: ${e.value}').join(', ')}',
                        style:
                            const TextStyle(fontSize: 16, color: Colors.teal),
                      ),
                      const SizedBox(height: 8),
                      Row(
                        children: [
                          ElevatedButton(
                            onPressed: () => extensionFlags.toggle('dark_mode'),
                            child: const Text('Toggle Dark Mode'),
                          ),
                          const SizedBox(width: 8),
                          ElevatedButton(
                            onPressed: () =>
                                extensionFlags.add('notifications', true),
                            child: const Text('Add Notifications'),
                          ),
                          const SizedBox(width: 8),
                          ElevatedButton(
                            onPressed: () => extensionFlags.clear(),
                            child: const Text('Clear All'),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ],
            ),
            _buildHeader('🔗 COMBINER EXAMPLES'),
            _buildSection(
              'Two Watchables Combined',
              'Combine multiple watchables with WatchableCombined2',
              [
                WatchableBuilder<String>(
                  watchable: WatchableCombined2(MainPage.name, MainPage.counter,
                      (n, c) => '$n has clicked $c times'),
                  builder: (value) => Text(
                    value,
                    style: const TextStyle(fontSize: 16, color: Colors.brown),
                  ),
                ),
              ],
            ),
            _buildSection(
              'Three Watchables Combined',
              'Advanced combination with multiple states',
              [
                WatchableBuilder<String>(
                  watchable: WatchableCombined3(
                      MainPage.name,
                      MainPage.counter,
                      MainPage.isLoading,
                      (n, c, loading) => loading
                          ? 'Loading...'
                          : '$n: $c clicks, Price: \$${MainPage.price.value.toStringAsFixed(2)}'),
                  builder: (value) => Text(
                    value,
                    style: const TextStyle(fontSize: 16, color: Colors.red),
                  ),
                ),
              ],
            ),
            _buildHeader('🎯 FORM VALIDATION EXAMPLE'),
            _buildSection(
              'Login Form',
              'Real-world form validation with combined watchables',
              [
                TextField(
                  onChanged: (value) => email.value = value,
                  decoration: const InputDecoration(
                    labelText: 'Email',
                    border: OutlineInputBorder(),
                  ),
                ),
                const SizedBox(height: 8),
                TextField(
                  onChanged: (value) => password.value = value,
                  obscureText: true,
                  decoration: const InputDecoration(
                    labelText: 'Password',
                    border: OutlineInputBorder(),
                  ),
                ),
                const SizedBox(height: 16),
                WatchableBuilder<String>(
                  watchable: WatchableCombined2(
                    email,
                    password,
                    (e, p) {
                      if (e.isEmpty) return 'Please enter email';
                      if (!e.contains('@')) return 'Invalid email format';
                      if (p.isEmpty) return 'Please enter password';
                      if (p.length < 6) {
                        return 'Password must be at least 6 characters';
                      }
                      return 'Valid';
                    },
                  ),
                  builder: (validation) {
                    final isValid = validation == 'Valid';
                    return Column(
                      children: [
                        Text(
                          validation,
                          style: TextStyle(
                            color: isValid ? Colors.green : Colors.red,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        const SizedBox(height: 8),
                        ElevatedButton(
                          onPressed: isValid
                              ? () {
                                  ScaffoldMessenger.of(context).showSnackBar(
                                    SnackBar(
                                        content: Text(
                                            'Login successful for ${email.value}!')),
                                  );
                                }
                              : null,
                          child: const Text('Login'),
                        ),
                      ],
                    );
                  },
                ),
              ],
            ),
            _buildHeader('🔔 ALWAYS NOTIFY FEATURE'),
            _buildSection(
              'Force Notifications on Identical Values',
              'Control whether identical value assignments trigger listeners',
              [
                WatchableBuilder<String>(
                  watchable: status,
                  builder: (value) => Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Status: $value',
                        style:
                            const TextStyle(fontSize: 18, color: Colors.blue),
                      ),
                      Text(
                        'Always Notify: ${status.isAlwaysNotifying ? "ON" : "OFF"}',
                        style: TextStyle(
                          fontSize: 14,
                          color: status.isAlwaysNotifying
                              ? Colors.green
                              : Colors.grey,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ],
                  ),
                ),
                const SizedBox(height: 12),
                Row(
                  children: [
                    ElevatedButton(
                      onPressed: () => status.value = 'Ready',
                      child: const Text('Set "Ready"'),
                    ),
                    const SizedBox(width: 8),
                    ElevatedButton(
                      onPressed: () => status.refresh(),
                      child: const Text('Refresh'),
                    ),
                  ],
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    ElevatedButton(
                      onPressed: () => status.alwaysNotify(
                          enabled: !status.isAlwaysNotifying),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: status.isAlwaysNotifying
                            ? Colors.red
                            : Colors.green,
                        foregroundColor: Colors.white,
                      ),
                      child: Text(status.isAlwaysNotifying
                          ? 'Disable Always Notify'
                          : 'Enable Always Notify'),
                    ),
                  ],
                ),
                const SizedBox(height: 8),
                const Text(
                  '💡 Try: Enable "Always Notify", then click "Set Ready" multiple times. '
                  'Each click will trigger a rebuild even with the same value!',
                  style: TextStyle(fontSize: 12, fontStyle: FontStyle.italic),
                ),
              ],
            ),
            _buildHeader('📊 shouldRebuild OPTIMIZATION'),
            _buildSection(
              'Optimized Rebuilds',
              'Using shouldRebuild to control when widgets update',
              [
                WatchableBuilder<int>(
                  watchable: MainPage.counter,
                  shouldRebuild: (previous, current) => current % 5 == 0,
                  builder: (value) => Container(
                    padding: const EdgeInsets.all(16),
                    decoration: BoxDecoration(
                      color: Colors.amber.shade100,
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Text(
                      'Only updates every 5 clicks: $value',
                      style: const TextStyle(
                          fontSize: 16, fontWeight: FontWeight.bold),
                    ),
                  ),
                ),
              ],
            ),
            _buildHeader('💡 API BENEFITS & COMPARISON'),
            Container(
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                color: Colors.green.shade50,
                borderRadius: BorderRadius.circular(8),
                border: Border.all(color: Colors.green.shade200),
              ),
              child: const Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    '✅ Const Watchable API Benefits:',
                    style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                  ),
                  SizedBox(height: 8),
                  Text(
                      '• const counter = Watchable(0) - compile-time optimization'),
                  Text('• Built on Flutter\'s proven ValueNotifier foundation'),
                  Text('• Direct value modification: counter.value++'),
                  Text('• Type-safe combiners and transformations'),
                  SizedBox(height: 12),
                  Text(
                    '🚀 Extension .watchable API Benefits:',
                    style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                  ),
                  SizedBox(height: 8),
                  Text('• final counter = 0.watchable - even more concise'),
                  Text(
                      '• Specialized methods: increment(), toggle(), add(), clear()'),
                  Text('• Type inference: no need to specify <T>'),
                  Text('• Collection helpers: items.add(), flags.toggle()'),
                  SizedBox(height: 12),
                  Text(
                    '⚡ Overall Benefits:',
                    style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                  ),
                  SizedBox(height: 8),
                  Text(
                      '• 70% less boilerplate than traditional state management'),
                  Text('• shouldRebuild optimization for performance'),
                  Text('• alwaysNotify() for forced updates when needed'),
                  Text('• refresh() method for one-time forced notifications'),
                  Text('• Deep collection equality with smart comparison'),
                  Text('• No global state complexity'),
                ],
              ),
            ),
            const SizedBox(height: 32),
          ],
        ),
      ),
    );
  }

  Widget _buildHeader(String title) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 16.0),
      child: Text(
        title,
        style: const TextStyle(
          fontSize: 20,
          fontWeight: FontWeight.bold,
          color: Colors.black87,
        ),
      ),
    );
  }

  Widget _buildSection(String title, String subtitle, List<Widget> children) {
    return Container(
      margin: const EdgeInsets.only(bottom: 24),
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.grey.shade50,
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: Colors.grey.shade200),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            title,
            style: const TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
              color: Colors.black87,
            ),
          ),
          const SizedBox(height: 4),
          Text(
            subtitle,
            style: TextStyle(
              fontSize: 14,
              color: Colors.grey.shade600,
              fontStyle: FontStyle.italic,
            ),
          ),
          const SizedBox(height: 12),
          ...children,
        ],
      ),
    );
  }
}

/// Example of the CounterState pattern for encapsulated state management
class CounterState {
  static const _counter = Watchable(0);

  // Public read-only access
  AbstractWatchable<int> get counter => _counter;

  // State modification methods
  void increment() => _counter.value++;
  void decrement() => _counter.value--;
  void reset() => _counter.value = 0;
  void addValue(int value) => _counter.value += value;
}
3
likes
160
points
194
downloads

Publisher

verified publisherdipendrasharma.com

Weekly Downloads

Production-ready, type-safe state management for Flutter. 10x performance improvement with advanced memory management and comprehensive error handling.

Repository (GitHub)
View/report issues

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

collection, flutter

More

Packages that depend on watchable