fairy 1.0.0 copy "fairy: ^1.0.0" to clipboard
fairy: ^1.0.0 copied to clipboard

A lightweight MVVM framework for Flutter with strongly-typed, reactive data binding state management library, Mainly focused on simplicity and ease of use.

1.0.0 ๐ŸŽ‰ #

Stable Release - A lightweight MVVM framework for Flutter with strongly-typed, reactive data binding.

This release consolidates all improvements from RC builds (rc.1, rc.2, rc.3) into a stable production-ready package.

โœจ New Features #

Async Command Execution State Tracking

  • isRunning property added to async commands for automatic execution state tracking
    • AsyncRelayCommand.isRunning: Tracks execution state (true while running, false otherwise)
    • AsyncRelayCommandWithParam<T>.isRunning: Same behavior for parameterized async commands
    • Automatic concurrent execution prevention: canExecute returns false while isRunning is true
    • Eliminates need for manual loading state management
    • Prevents double-click bugs automatically
    • Enables easy loading indicators in UI

Command Widget API Enhancement

  • 4th parameter isRunning added to all Command builder signatures for consistency
    • Command<TViewModel>: Builder now receives isRunning (always false for sync commands)
    • Command.param<TViewModel, TParam>: Builder now receives isRunning (always false for sync commands)
    • Async commands return actual isRunning state from the command
    • Consistent API across all command types

Overlay ViewModel Bridging

  • FairyBridge: New widget to bridge ViewModels to overlay widget trees
    • Solves the problem of dialogs, bottom sheets, and menus creating separate widget trees
    • Captures parent context's FairyScope and makes it available to overlay
    • Enables Bind and Command widgets to work seamlessly in overlays
    • Gracefully falls back to FairyLocator if no FairyScope found

UI Widgets API Enhancement

  • Bind.viewModel<TViewModel>: Auto-tracking data binding for multiple properties
    • Eliminates need for manual selectors when displaying multiple properties
    • Automatically tracks all accessed properties and rebuilds only when they change
    • Achieves superior selective rebuild efficiency (100% accuracy)
    • 4-10% faster than competitors while maintaining perfect selectivity
  • Command.param<TViewModel, TParam>: Factory constructor for parameterized commands
    • Provides consistent API alongside Command<TViewModel>
    • Simplifies parameterized command binding in UI
    • Completes the "Learn just 2 widgets" philosophy

Recursive Deep Equality for Collections

  • Built-in recursive deep equality for all collection types without external dependencies
    • Automatically handles arbitrary nesting depth: List<Map<String, List<int>>>
    • Works with List, Map, Set, and Iterable at any level
    • Custom types use their == operator when nested in collections
    • Zero configuration needed - deep equality enabled by default
  • Equals utility class for custom equality implementations
    • Equals.deepCollectionEquals() - Recursive equality for any collection type
    • Equals.deepCollectionHash() - Recursive hash code generation
    • Collection-specific methods: listEquals, mapEquals, setEquals
    • Hash methods: listHash, mapHash, setHash
  • ObservableProperty.deepEquality parameter (default: true)
    • Primitive types use ==, collections use deep equality automatically
    • Override == operator is optional for custom types

๐Ÿ”„ Breaking Changes #

Command.param Parameter Type Change

  • BREAKING: Command.param parameter changed from static TParam to function TParam Function()
    • Reason: Enables reactive parameter evaluation on rebuild
    • Before: parameter: todoId,
    • After: parameter: () => todoId,
    • For reactive controller values, wrap with ValueListenableBuilder

Command Builder Signature Update

  • BREAKING: All Command builder signatures now include 4th isRunning parameter
    • Before: builder: (context, execute, canExecute) { ... }
    • After: builder: (context, execute, canExecute, isRunning) { ... }
    • Applies to both Command<TViewModel> and Command.param<TViewModel, TParam>
    • isRunning is always present but only meaningful for async commands (false for sync)

Removed Extensions

  • BREAKING: Removed ObservableObjectExtensions for creating properties/commands
    • Before: final counter = observableProperty<int>(0);
    • After: final counter = ObservableProperty<int>(0);
    • Reason: Direct type usage is clearer, more discoverable, and follows Dart conventions
    • Replace all lowercase helpers (observableProperty, computedProperty, relayCommand, etc.) with direct constructors

Command Constructor Changes

  • BREAKING: Removed parent parameter from all command constructors
    • Before: RelayCommand(execute, parent: this, canExecute: ...)
    • After: RelayCommand(execute, canExecute: ...)
    • Reason: Auto-disposal makes parent tracking unnecessary

๐Ÿš€ Performance Improvements #

Comprehensive benchmarks show exceptional performance achievements:

  • ๐Ÿฅ‡ Memory Management: Highly optimized cleanup and disposal system
  • ๐Ÿฅ‡ Selective Rebuilds: Exceptional performance with explicit Bind selectors
  • ๐Ÿฅ‡ Auto-Binding Performance: Bind.viewModel delivers superior speed while maintaining perfect selectivity
  • Unique Achievement: 100% rebuild efficiency with Bind.viewModel - only rebuilds when accessed properties change
  • Deep equality optimized with fast-path identical() checks and efficient recursive comparison

๐Ÿ“š Documentation #

  • Comprehensive "Learn just 2 widgets" positioning (Bind and Command)
  • Added examples for all new features: isRunning, FairyBridge, Bind.viewModel, Command.param
  • Updated all Command widget examples with 4th isRunning parameter
  • Added ValueListenableBuilder pattern for reactive parameters
  • Deep equality usage examples for collections and custom types
  • Enhanced best practices section with memory leak warnings
  • Complete API reference in llms.txt
  • Added benchmark results demonstrating performance leadership

๐Ÿงช Testing #

  • 401 tests passing with comprehensive coverage
  • Tests for async command execution state tracking
  • Tests for FairyBridge widget with overlay scenarios
  • Tests for Bind.viewModel auto-tracking functionality
  • Tests for Command.param factory constructor
  • Tests for recursive deep equality (43 comprehensive tests)
  • All breaking changes validated with updated tests

๐ŸŽฏ Framework Philosophy #

  • "Learn just 2 widgets": Bind for data, Command for actions
  • No code generation: Zero build_runner dependency
  • Type-safe: Strong typing throughout the API
  • Automatic disposal: No memory leaks with proper patterns
  • Zero external dependencies: Only Flutter SDK required
  • Built-in deep equality: No external packages needed

1.0.0-rc.3 #

โœจ New Features #

Async Command Execution State Tracking

  • isRunning property added to async commands for automatic execution state tracking
    • AsyncRelayCommand.isRunning: Tracks execution state (true while running, false otherwise)
    • AsyncRelayCommandWithParam<T>.isRunning: Same behavior for parameterized async commands
    • Automatic concurrent execution prevention: canExecute returns false while isRunning is true
    • Eliminates need for manual loading state management
    • Prevents double-click bugs automatically
    • Enables easy loading indicators in UI

Command Widget API Enhancement

  • 4th parameter isRunning added to all Command builder signatures for consistency
    • Command<TViewModel>: Builder now receives isRunning (always false for sync commands)
    • Command.param<TViewModel, TParam>: Builder now receives isRunning (always false for sync commands)
    • Async commands return actual isRunning state from the command
    • Consistent API across all command types
// Async command with loading indicator
Command<DataViewModel>(
  command: (vm) => vm.fetchCommand,
  builder: (context, execute, canExecute, isRunning) {
    if (isRunning) return CircularProgressIndicator();
    return ElevatedButton(
      onPressed: execute,
      child: Text('Fetch Data'),
    );
  },
)

// Sync command (isRunning always false)
Command<TodoViewModel>(
  command: (vm) => vm.deleteCommand,
  builder: (context, execute, canExecute, isRunning) {
    return IconButton(
      onPressed: canExecute ? execute : null,
      icon: Icon(Icons.delete),
    );
  },
)

Overlay ViewModel Bridging

  • FairyBridge: New widget to bridge ViewModels to overlay widget trees
    • Solves the problem of dialogs, bottom sheets, and menus creating separate widget trees
    • Captures parent context's FairyScope and makes it available to overlay
    • Enables Bind and Command widgets to work seamlessly in overlays
    • Gracefully falls back to FairyLocator if no FairyScope found
void _showDialog(BuildContext context) {
  showDialog(
    context: context,
    builder: (_) => FairyBridge(
      context: context, // Makes parent FairyScope available
      child: AlertDialog(
        actions: [
          Command<MyViewModel>(
            command: (vm) => vm.saveCommand,
            builder: (ctx, execute, canExecute, isRunning) =>
              TextButton(onPressed: execute, child: Text('Save')),
          ),
        ],
      ),
    ),
  );
}

๐Ÿ”„ Breaking Changes #

Command.param Parameter Type Change

  • BREAKING: Command.param parameter changed from static TParam to function TParam Function()
    • Reason: Enables reactive parameter evaluation on rebuild
    • Before: parameter: todoId,
    • After: parameter: () => todoId,
    • For reactive controller values, wrap with ValueListenableBuilder:
// Before (static parameter - doesn't react to changes)
Command.param<TodoViewModel, String>(
  parameter: controller.text, // Static value
  builder: (context, execute, canExecute) { ... },
)

// After (reactive parameter)
ValueListenableBuilder<TextEditingValue>(
  valueListenable: controller,
  builder: (context, value, _) {
    return Command.param<TodoViewModel, String>(
      parameter: () => value.text, // Reactive to text changes
      builder: (context, execute, canExecute, isRunning) { ... },
    );
  },
)

Command Builder Signature Update

  • BREAKING: All Command builder signatures now include 4th isRunning parameter
    • Before: builder: (context, execute, canExecute) { ... }
    • After: builder: (context, execute, canExecute, isRunning) { ... }
    • Applies to both Command<TViewModel> and Command.param<TViewModel, TParam>
    • isRunning is always present but only meaningful for async commands (false for sync)

๐Ÿงช Testing #

  • 401 tests passing
  • Updated 5 tests for new concurrent execution prevention behavior
  • Added tests for isRunning state tracking
  • Added tests for Command widget isRunning parameter

๐Ÿ“š Documentation

  • Added comprehensive examples for isRunning usage
  • Added FairyBridge widget documentation and examples
  • Updated all Command widget examples with 4th parameter
  • Added ValueListenableBuilder pattern for reactive parameters
  • Updated llms.txt with new API surface

1.0.0-rc.2 #

โœจ New Features #

Recursive Deep Equality for Collections

  • Built-in recursive deep equality for all collection types without external dependencies
    • Automatically handles arbitrary nesting depth: List<Map<String, List<int>>>
    • Works with List, Map, Set, and Iterable at any level
    • Custom types use their == operator when nested in collections
    • Zero configuration needed - deep equality enabled by default

Equals Utility Class

  • Equals.deepCollectionEquals(Object? e1, Object? e2): Recursive equality for any collection type
  • Equals.deepCollectionHash(Object? o): Recursive hash code generation
  • Collection-specific methods: listEquals, mapEquals, setEquals with deep comparison
  • Hash methods: listHash, mapHash, setHash for consistent hash codes
  • Equals.deepEquals<T>(): Factory method for ObservableProperty equality parameter

๐Ÿ”ง API Enhancements #

ObservableProperty Deep Equality

  • deepEquality: bool parameter (default: true) for automatic collection comparison
    • Primitive types: ObservableProperty<int>(0) - uses ==
    • Collections: ObservableProperty<List<int>>([1, 2, 3]) - uses deep equality
    • Custom types: Override == operator is optional (only needed for value-based equality)

๐Ÿ“š Developer Experience #

Optional Equality Override

  • Simplified workflow: No need to override == for custom types with collections
    • Collections are compared deeply automatically
    • Override == only if you want value-based equality instead of reference equality
    • Use Equals utilities in custom == implementations when overriding
// Works automatically without custom ==
final todos = ObservableProperty<List<String>>(['Task 1', 'Task 2']);
todos.value = ['Task 1', 'Task 2'];  // โœ… No rebuild - deep equality

// Custom type - override == is optional
class Project {
  final String name;
  final List<String> tasks;
  
  // OPTIONAL: Override for value-based equality
  @override
  bool operator ==(Object other) =>
    identical(this, other) ||
    other is Project &&
      name == other.name &&
      Equals.listEquals(tasks, other.tasks);
}

๐Ÿงช Testing #

  • 387 tests passing (up from 344)
  • Added 43 comprehensive deep equality tests:
    • Simple collections (List, Map, Set)
    • Nested structures with 2-3 levels of nesting
    • Mixed nested structures: List<Map<String, List<int>>>
    • Sets with nested lists
    • Custom types with and without equality overrides
    • Nested custom types containing collections
    • Performance and memory tests

๐Ÿš€ Performance #

  • Deep equality optimized with fast-path identical() checks
  • Recursive comparison with efficient callback pattern
  • No performance impact on primitive types
  • Benchmarks show excellent overall performance maintained

1.0.0-rc.1 #

๐ŸŽ‰ Major Release Candidate #

This release represents a major milestone with significant API improvements, enhanced performance, and comprehensive testing.

โœจ New Features #

UI Widgets API Enhancement

  • Bind.viewModel<TViewModel>: New factory constructor for automatic property tracking
    • Eliminates need for manual selectors when displaying multiple properties
    • Automatically tracks all accessed properties and rebuilds only when they change
    • Achieves great selective rebuild efficiency over other state management solutions
    • 4-10% faster than competitors while maintaining perfect selectivity
  • Command.param<TViewModel, TParam>: New factory constructor for parameterized commands
    • Provides consistent API alongside Command<TViewModel>
    • Simplifies parameterized command binding in UI
    • Completes the "2 widgets" framework positioning

๐Ÿ”„ Breaking Changes #

Removed Extensions

  • BREAKING: Removed ObservableObjectExtensions for creating properties/commands
    • Before (Properties): final counter = observableProperty<int>(0);
    • After (Properties): final counter = ObservableProperty<int>(0);
    • Before (Commands): late final saveCommand = relayCommand(_save);
    • After (Commands): late final saveCommand = RelayCommand(_save);
    • Reason: Direct type usage is clearer, more discoverable, and follows Dart conventions
    • Migration: Replace all observableProperty<T>() with ObservableProperty<T>()
    • Migration: Replace all computedProperty<T>() with ComputedProperty<T>()
    • Migration: Replace all command helpers (relayCommand, asyncRelayCommand, etc.) with direct constructors (RelayCommand, AsyncRelayCommand, etc.)

Command Constructor Changes

  • BREAKING: Removed parent parameter from all command constructors
    • Before: RelayCommand(execute, parent: this, canExecute: ...)
    • After: RelayCommand(execute, canExecute: ...)
    • Reason: Auto-disposal makes parent tracking unnecessary
    • Migration: Remove parent: this from all command instantiations

๐Ÿš€ Performance Improvements #

Comprehensive benchmarks show significant performance achievements:

  • ๐Ÿฅ‡ Memory Management: Highly optimized cleanup and disposal system
  • ๐Ÿฅ‡ Selective Rebuilds: Exceptional performance with explicit Bind selectors
  • ๐Ÿฅ‡ Auto-tracking Performance: Bind.viewModel delivers superior speed while maintaining perfect selectivity
  • Unique Achievement: 100% rebuild efficiency with Bind.viewModel - only rebuilds when accessed properties change

๐Ÿ“š Documentation Improvements #

  • Updated all examples to use direct type constructors
  • Added comprehensive Bind.viewModel usage examples
  • Added Command.param examples throughout documentation
  • "2 widgets" framework (Learn just Bind and Command)
  • Enhanced best practices section with memory leak warnings
  • Added benchmark results to main README

๐Ÿงช Testing #

  • 344 tests passing (up from 299)
  • Added comprehensive tests for new Bind.viewModel functionality
  • Added tests for Command.param factory constructor
  • All existing functionality validated with updated API

๐Ÿ“ฆ What's Next #

The 1.0.0 stable release is planned after community feedback on this RC. Please report any issues or suggestions!


0.5.0+2 #

  • Improved documentation and fixed minor typos.

0.5.0+1 #

  • Improved documentation and fixed minor typos.

0.5.0 #

Initial release of Fairy - A lightweight MVVM framework for Flutter.

Features #

Core Primitives

  • ObservableObject: Base ViewModel class with clean MVVM API
    • onPropertyChanged() for manual notifications
    • propertyChanged(listener) method returning disposer function
    • setProperty<T>() helper for batch updates with change detection
    • Auto-disposal: Properties created during construction are automatically disposed
  • ObservableProperty: Strongly-typed reactive properties
    • Automatic change notifications with custom equality support
    • propertyChanged(listener) for subscribing to property changes (returns disposer)
    • Auto-disposal when parent ObservableObject is disposed
  • ComputedProperty: Derived properties with automatic dependency tracking
    • Read-only computed values based on other properties
    • Automatic updates when dependencies change
    • Auto-disposal when parent ObservableObject is disposed

Commands

  • RelayCommand: Synchronous commands with optional canExecute validation
  • AsyncRelayCommand: Asynchronous commands with automatic isRunning state
  • RelayCommandWithParam: Parameterized commands for actions requiring input
  • AsyncRelayCommandWithParam: Async parameterized commands
  • All commands use named parameters: execute:, canExecute:, parent:
  • notifyCanExecuteChanged() method to re-evaluate canExecute conditions
  • canExecuteChanged(listener) method for subscribing to canExecute changes (returns disposer function)

Dependency Injection

  • FairyLocator: Global singleton registry for app-wide services
    • registerSingleton<T>() for singleton registration
    • registerFactory<T>() for factory registration
    • get<T>() for service resolution
    • unregister<T>() for cleanup
  • FairyScope: Widget-scoped DI with automatic disposal
    • Scoped ViewModels auto-disposed when widget tree is removed
    • Supports both create and instance parameters
  • Fairy (ViewModelLocator): Unified resolution checking scope โ†’ global โ†’ exception
    • Fairy.of<T>(context): Idiomatic Flutter API for resolving ViewModels (similar to Provider.of, Theme.of)
    • Fairy.maybeOf<T>(context): Optional resolution returning null if not found

UI Binding

  • Bind<TViewModel, TValue>: Automatic one-way/two-way binding detection
    • Returns ObservableProperty<T> โ†’ two-way binding with update callback
    • Returns raw T โ†’ one-way binding (read-only)
    • Type-safe selector/builder contracts
  • Command: Command binding with automatic canExecute reactivity
  • CommandWithParam<TViewModel, TParam>: Parameterized command binding

Auto-Disposal System

  • Parent Parameter: Properties, commands, and computed properties accept optional parent parameter
    • Pass parent: this in constructor to enable automatic disposal
    • Children are registered with parent and disposed automatically
    • Debug warnings shown when parent is not provided
    • Nested ObservableObject instances must be disposed manually

Memory Management #

  • Auto-disposal: ObservableProperty, ComputedProperty, and Commands automatically disposed when parent parameter is provided
  • Nested ViewModels Exception: Nested ObservableObject instances require manual disposal
  • Manual Listeners: Always capture disposer from propertyChanged() and canExecuteChanged() calls to avoid memory leaks
  • Use Bind and Command widgets for UI (automatic lifecycle management)

Best Practices #

  • โš ๏ธ Memory Leak Prevention: Always capture disposer from manual propertyChanged() and canExecuteChanged() calls
  • Pass parent: this to properties, commands, and computed properties for auto-disposal
  • Nested ViewModels require explicit manual disposal
  • Call command.notifyCanExecuteChanged() when canExecute dependencies change
  • Use command.canExecuteChanged(listener) to listen to canExecute state changes
  • Selectors must return stable property references
  • Use FairyScope for page-level ViewModels (handles disposal automatically)
  • Use named parameters for commands: execute:, canExecute:, parent:

Documentation #

  • Comprehensive README with quick start guide
  • Auto-disposal explanation and migration patterns
  • Complete API reference with examples
  • Example app demonstrating MVVM patterns

Testing #

  • Comprehensive unit and widget tests with 100% passing rate
  • Tests cover all core primitives, DI patterns, UI bindings, and auto-disposal
  • Test structure mirrors library organization
7
likes
0
points
41
downloads

Publisher

verified publishercircuids.com

Weekly Downloads

A lightweight MVVM framework for Flutter with strongly-typed, reactive data binding state management library, Mainly focused on simplicity and ease of use.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on fairy