ease_state_helper 0.3.0 copy "ease_state_helper: ^0.3.0" to clipboard
ease_state_helper: ^0.3.0 copied to clipboard

A simple Flutter state management helper that makes using Flutter's internal state management easier with InheritedWidget and code generation.

Ease State Helper #

A lightweight helper library that makes Flutter’s built-in state management easier to use.

pub package License: MIT Flutter Dart codecov


Table of Contents #


Why Ease? #

Feature Ease Provider Riverpod Bloc
Built on Flutter primitives ✅ InheritedModel
Code generation optional ⚠️ Recommended
Selective rebuilds select()
DevTools integration
Learning curve Low Low Medium High
Boilerplate Minimal Minimal Medium High

Ease is ideal when you want:

  • Simple state management without heavy dependencies
  • Flutter's native patterns (InheritedWidget) with less boilerplate

Installation #

Minimal Setup (VS Code Extension) #

dependencies:
  ease_state_helper: ^0.1.0

Full Setup (Code Generation) #

dependencies:
  ease_state_helper: ^0.1.0
  ease_annotation: ^0.1.0

dev_dependencies:
  ease_generator: ^0.1.0
  build_runner: ^2.4.0

Quick Start #

1. Create a ViewModel #

import 'package:ease_annotation/ease_annotation.dart';
import 'package:ease_state_helper/ease_state_helper.dart';

part 'counter_view_model.ease.dart';

@ease
class CounterViewModel extends StateNotifier<int> {
  CounterViewModel() : super(0);

  void increment() => state++;
  void decrement() => state--;
  void reset() => state = 0;
}

2. Run Code Generation #

dart run build_runner build

This generates:

  • counter_view_model.ease.dart - Provider and context extensions

3. Register Providers #

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

void main() {
  runApp(
    CounterViewModelProvider(
      child: const MyApp(),
    ),
  );
}

4. Use in Widgets #

class CounterScreen extends StatelessWidget {
  const CounterScreen({super.key});

  @override
  Widget build(BuildContext context) {
    // Watch - rebuilds when state changes
    final counter = context.counterViewModel;

    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: Text(
          '${counter.state}',
          style: Theme.of(context).textTheme.displayLarge,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // Read - no rebuild, use in callbacks
        onPressed: () => context.readCounterViewModel().increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

Core Concepts #

StateNotifier<T> #

Base class for all ViewModels. Extends ChangeNotifier with a typed state property.

class CartViewModel extends StateNotifier<CartState> {
  CartViewModel() : super(const CartState());

  // Direct assignment
  void clear() => state = const CartState();

  // Update based on current state
  void addItem(Product product) {
    state = state.copyWith(
      items: [...state.items, CartItem(product: product)],
    );
  }

  // Using update helper
  void toggleLoading() {
    update((current) => current.copyWith(isLoading: !current.isLoading));
  }
}

Provider Nesting #

Use EaseScope to nest multiple providers:

void main() {
  runApp(
    EaseScope(
      providers: [
        (child) => CounterViewModelProvider(child: child),
        (child) => CartViewModelProvider(child: child),
      ],
      child: const MyApp(),
    ),
  );
}

Or nest them manually:

void main() {
  runApp(
    CounterViewModelProvider(
      child: CartViewModelProvider(
        child: const MyApp(),
      ),
    ),
  );
}

@ease Annotation #

Marks a StateNotifier class for code generation.

// Global provider - included in $easeProviders
@ease
class AppViewModel extends StateNotifier<AppState> { ... }

// Local provider - manually placed in widget tree
@Ease(local: true)
class FormViewModel extends StateNotifier<FormState> { ... }

API Reference #

Context Extensions #

For each ViewModel, these extensions are generated:

Method Subscribes Rebuilds Use Case
context.myViewModel ✅ Yes On any change Display state in UI
context.readMyViewModel() ❌ No Never Callbacks, event handlers
context.selectMyViewModel((s) => s.field) ✅ Partial When selected value changes Optimized rebuilds
context.listenOnMyViewModel((prev, next) => ...) ❌ No Never Side effects

Watch (Subscribe) #

// Rebuilds entire widget when ANY state property changes
final cart = context.cartViewModel;
Text('Items: ${cart.state.items.length}');
Text('Total: \$${cart.state.total}');

Read (No Subscribe) #

// Never rebuilds - use for callbacks and event handlers
ElevatedButton(
  onPressed: () {
    final cart = context.readCartViewModel();
    cart.addItem(product);
  },
  child: const Text('Add to Cart'),
)

Select (Partial Subscribe) #

// Only rebuilds when itemCount changes
final itemCount = context.selectCartViewModel((s) => s.itemCount);

// With custom equality for complex types
final items = context.selectCartViewModel(
  (s) => s.items,
  equals: (a, b) => listEquals(a, b),
);

Listen (Side Effects) #

@override
void initState() {
  super.initState();

  // Listen for errors and show snackbar
  context.listenOnCartViewModel((prev, next) {
    if (next.error != null && prev.error != next.error) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(next.error!)),
      );
    }
  });

  // No dispose needed - auto-cleanup when widget unmounts
}

Advanced Usage #

Complex State with copyWith #

class CartState {
  final List<CartItem> items;
  final bool isLoading;
  final String? error;

  const CartState({
    this.items = const [],
    this.isLoading = false,
    this.error,
  });

  // Computed properties
  int get itemCount => items.fold(0, (sum, item) => sum + item.quantity);
  double get subtotal => items.fold(0, (sum, item) => sum + item.total);
  double get tax => subtotal * 0.1;
  double get total => subtotal + tax;

  CartState copyWith({
    List<CartItem>? items,
    bool? isLoading,
    String? error,
  }) {
    return CartState(
      items: items ?? this.items,
      isLoading: isLoading ?? this.isLoading,
      error: error,
    );
  }
}

Local/Scoped Providers #

For state that should be scoped to a specific part of the widget tree:

@Ease(local: true)
class FormViewModel extends StateNotifier<FormState> {
  FormViewModel() : super(const FormState());
  // ...
}

// Manually wrap the subtree that needs this state
class MyFormScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FormViewModelProvider(
      child: const FormContent(),
    );
  }
}

Middleware #

Add logging or other middleware to all state changes:

void main() {
  StateNotifier.middleware = [
    LoggingMiddleware(),
    // Add custom middleware
  ];
  runApp(
    EaseScope(
      providers: [
        (child) => CounterViewModelProvider(child: child),
      ],
      child: const MyApp(),
    ),
  );
}

DevTools Support #

Debug your Ease states in Flutter DevTools.

Setup #

dev_dependencies:
  ease_devtools_extension: ^0.1.0
void main() {
  initializeEaseDevTool(); // Enable DevTools integration
  runApp(
    EaseScope(
      providers: [
        (child) => CounterViewModelProvider(child: child),
      ],
      child: const MyApp(),
    ),
  );
}

Features #

  • State Inspector - View all registered states and current values
  • History Timeline - Track state changes with timestamps
  • Action Tracking - See what triggered each state change
  • Filter Support - Filter history by state type

Packages #

Package Description pub.flutter-io.cn
ease_state_helper Core runtime library pub
ease_annotation @Ease() annotation pub
ease_generator Code generator pub
ease_devtools_extension DevTools integration pub

Examples #

Check out the example apps in the repository:

Example Description
example Comprehensive examples: Counter, Todo, Cart, Auth, Theme
shopping_app Real-world e-commerce app with FakeStore API

Running Examples #

cd apps/example
flutter pub get
dart run build_runner build
flutter run

VS Code Extension #

For a no-code-generation workflow:

  1. Install Ease State Helper extension from VS Code marketplace
  2. Right-click folder → Ease: New ViewModel
  3. Enter name and state type

The extension generates both .dart and .ease.dart files without needing build_runner.


Contributing #

Contributions are welcome!

Here's how you can help:

  • 🐛 Bug reports - Found an edge case or unexpected behavior? Open an issue
  • 📖 Documentation - Improve guides, fix typos, or add code examples
  • Features - Have an idea? Discuss it in an issue first, then submit a PR
  • 🧪 Tests - Help improve test coverage

Development Setup #

git clone https://github.com/y3l1n4ung/ease.git
cd ease
melos bootstrap
melos run test:all

See the Contributing Guide for detailed guidelines.


License #

This project is licensed under the MIT License - see the LICENSE file for details.

1
likes
160
points
106
downloads

Publisher

verified publisherwidget-lab.dev

Weekly Downloads

A simple Flutter state management helper that makes using Flutter's internal state management easier with InheritedWidget and code generation.

Repository (GitHub)
View/report issues
Contributing

Topics

#state-management #flutter #inherited-widget #code-generation

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on ease_state_helper