declare 1.1.0 copy "declare: ^1.1.0" to clipboard
declare: ^1.1.0 copied to clipboard

retracted

A lightweight, SwiftUI-inspired Prop management library for Flutter. Powered by Prop, ViewModel, and Published.

๐Ÿ“ฆ Declare #

SwiftUI-inspired reactive state management for Flutter โ€” powered by Prop<T>, Published<T>, Computed<T>, and ViewModel concepts.

Simple. Lightweight. Declarative. No external dependencies. Auto-reactive โ€” no more Observer widgets needed!


โœจ Features #

โœ… Auto-reactive UI โ€” access viewModel.count.value directly, rebuilds automatically
โœ… Minimal syntax inspired by SwiftUI and modern reactive frameworks
โœ… Prop<T> โ€“ reactive local state with automatic notification
โœ… Published<T> โ€“ nullable reactive state that notifies parent ViewModel
โœ… Computed<T> โ€“ derived reactive values based on dependencies
โœ… ViewModel โ€“ lifecycle-aware logic container managing reactive states
โœ… Declare<T> โ€“ widget with automatic dependency tracking and rebuilding
โœ… Pure Dart and Flutter โ€” no third-party dependencies
โœ… Clean MVVM architecture with excellent developer experience


๐Ÿš€ What's New in Auto-Reactive Mode #

// โœ… New way - Auto-reactive!
Text('Count: ${viewModel.count.value}') // Rebuilds automatically!

๐Ÿงฑ Getting Started #

Add declare to your pubspec.yaml:

dependencies:
  declare: ^1.1.0

Basic Example - Auto-Reactive Counter #

class CounterViewModel extends ViewModel {
  late final count = state(0);
  late final doubleCount = computed(() => count.value * 2, [count]);
  late final message = state('Hello World');
  
  void increment() => count.value++;
  void decrement() => count.value--;
  void updateMessage(String newMessage) => message.value = newMessage;
  
  @override
  void onInit() {
    print('CounterViewModel initialized');
  }
  
  @override
  void onDispose() {
    print('CounterViewModel disposed');
  }
}

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Declare<CounterViewModel>(
      create: () => CounterViewModel(),
      builder: (context, viewModel) {
        return Scaffold(
          appBar: AppBar(title: Text('Auto-Reactive Counter')),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // These automatically rebuild when values change! ๐ŸŽ‰
                Text('Count: ${viewModel.count.value}'),
                Text('Double: ${viewModel.doubleCount.value}'),
                Text('Message: ${viewModel.message.value}'),
                SizedBox(height: 20),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    ElevatedButton(
                      onPressed: viewModel.decrement,
                      child: Text('-'),
                    ),
                    ElevatedButton(
                      onPressed: viewModel.increment,
                      child: Text('+'),
                    ),
                  ],
                ),
                SizedBox(height: 20),
                ElevatedButton(
                  onPressed: () => viewModel.updateMessage('Updated!'),
                  child: Text('Update Message'),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

Advanced Example - Shopping Cart #

class Product {
  final String name;
  final double price;
  Product(this.name, this.price);
}

class CartViewModel extends ViewModel {
  late final items = state<List<Product>>([]);
  late final total = computed(() => 
    items.value.fold(0.0, (sum, item) => sum + item.price), 
    [items]
  );
  late final itemCount = computed(() => items.value.length, [items]);
  
  void addItem(Product product) {
    items.value = [...items.value, product];
  }
  
  void removeItem(Product product) {
    items.value = items.value.where((item) => item != product).toList();
  }
  
  void clearCart() {
    items.value = [];
  }
}

class CartView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Declare<CartViewModel>(
      create: () => CartViewModel(),
      builder: (context, viewModel) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Cart (${viewModel.itemCount.value})'),
          ),
          body: Column(
            children: [
              Text('Total: \$${viewModel.total.value.toStringAsFixed(2)}'),
              Expanded(
                child: ListView.builder(
                  itemCount: viewModel.items.value.length,
                  itemBuilder: (context, index) {
                    final item = viewModel.items.value[index];
                    return ListTile(
                      title: Text(item.name),
                      subtitle: Text('\$${item.price}'),
                      trailing: IconButton(
                        icon: Icon(Icons.remove),
                        onPressed: () => viewModel.removeItem(item),
                      ),
                    );
                  },
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

๐Ÿ“š API Overview #

Core Classes #

Prop<T> โ€“ Reactive wrapper around a value with update and transform methods

final count = Prop<int>(0);
count.value = 5; // Automatically notifies listeners
count.increment(); // Built-in helper for numbers
count.transform((current) => current * 2); // Transform current value

Published<T> โ€“ Nullable reactive state that notifies parent ViewModel

final user = Published<User>(); // Starts as null
user.value = User('John');
user.clear(); // Set back to null
print(user.hasValue); // Check if value exists

Computed<T> โ€“ Derived reactive value computed from dependencies

final fullName = computed(() => '${firstName.value} ${lastName.value}', [firstName, lastName]);
// Automatically updates when firstName or lastName changes

ViewModel โ€“ Base class managing reactive states with lifecycle hooks

class MyViewModel extends ViewModel {
  late final count = state(0); // Creates Prop<int>
  late final user = published<User>(); // Creates Published<User>
  late final doubleCount = computed(() => count.value * 2, [count]);
  
  @override
  void onInit() { /* Called when ViewModel is created */ }
  
  @override
  void onDispose() { /* Called when ViewModel is disposed */ }
}

Declare<T> โ€“ Widget that creates and manages ViewModel lifecycle

Declare<MyViewModel>(
  create: () => MyViewModel(),
  builder: (context, viewModel) {
    return Text('Count: ${viewModel.count.value}'); // Auto-reactive!
  },
);

Extensions #

Reactive Extensions โ€“ Chain operations on any ValueListenable<T>

final doubled = count.map((value) => value * 2);
final combined = count.combine(name, (c, n) => '$n: $c');

Prop Extensions โ€“ Convenient methods for common operations

count.increment(); // For numbers
count.decrement(); // For numbers
isVisible.toggle(); // For booleans

List Extensions โ€“ Easy list manipulation

final items = state<List<String>>([]);
items.add('New item');
items.remove('Old item');
items.clear();
items.updateAt(0, 'Updated item');

๐Ÿ”„ How Auto-Reactivity Works #

  1. Dependency Tracking: When you access viewModel.count.value during a build, Declare automatically tracks this dependency
  2. Change Detection: When count.value changes, all widgets that accessed it are marked for rebuild
  3. Efficient Updates: Only the specific Declare widgets that depend on changed values are rebuilt
  4. No Manual Subscriptions: No need to wrap values in Observer widgets or manually manage listeners

โš™๏ธ Lifecycle #

  • ViewModel.onInit() is called once when Declare initializes
  • ViewModel.onDispose() is called when Declare disposes
  • Reactive states automatically notify their parent ViewModel
  • Auto-tracking subscribes to accessed values during build phase
  • All reactive resources are automatically cleaned up on disposal

๐ŸŽฏ Best Practices #

โœ… Do #

// Use direct value access in Declare
Text('Count: ${viewModel.count.value}')

// Create computed values for derived state
late final total = computed(() => items.value.fold(0, (a, b) => a + b.price), [items]);

// Use lifecycle hooks for initialization
@override
void onInit() {
  loadUserData();
}

โŒ Don't #


// Don't forget to use .value when accessing
Text('Count: ${viewModel.count}') // Missing .value!

// Don't mutate state directly in build method
Text('Count: ${viewModel.count.value++}') // Side effect in build!

๐Ÿš€ Performance Benefits #

  • Granular Updates: Only widgets that access changed values rebuild
  • Automatic Optimization: No need to manually optimize with Consumer or Selector patterns
  • Minimal Overhead: Dependency tracking adds negligible performance cost
  • Memory Efficient: Automatic cleanup prevents memory leaks

๐Ÿงช Testing #

ViewModels are easy to test since they're pure Dart classes:

void main() {
  group('CounterViewModel', () {
    late CounterViewModel viewModel;
    
    setUp(() {
      viewModel = CounterViewModel();
    });
    
    tearDown(() {
      viewModel.dispose();
    });
    
    test('should increment count', () {
      expect(viewModel.count.value, 0);
      viewModel.increment();
      expect(viewModel.count.value, 1);
    });
    
    test('should compute double count', () {
      viewModel.count.value = 5;
      expect(viewModel.doubleCount.value, 10);
    });
  });
}

๐ŸŽจ Comparison with Other Solutions #

Feature Declare Provider Riverpod Bloc
Auto-reactive โœ… โŒ โŒ โŒ
Boilerplate Minimal Medium Low High
Learning Curve Easy Easy Medium Hard
Performance Excellent Good Excellent Excellent
Testing Easy Medium Easy Easy
DevTools Basic Excellent Excellent Excellent

๐Ÿ”ฎ Roadmap #

  • โŒ DevTools integration for state inspection
  • โŒ Async state management utilities
  • โŒ State persistence helpers
  • โŒ Time-travel debugging
  • โŒ React DevTools-style state inspector

๐Ÿค Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.


๐Ÿ“„ License #

This project is licensed under the MIT License.


Made with โค๏ธ for the Flutter community

Declare: Where reactive meets declarative ๐Ÿš€

3
likes
0
points
12
downloads

Publisher

unverified uploader

Weekly Downloads

A lightweight, SwiftUI-inspired Prop management library for Flutter. Powered by Prop, ViewModel, and Published.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on declare