Ease State Helper
A lightweight helper library that makes Flutter’s built-in state management easier to use.
Table of Contents
- Why Ease?
- Installation
- Quick Start
- Core Concepts
- API Reference
- Advanced Usage
- DevTools Support
- Packages
- Examples
- Contributing
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 | |
| ease_annotation | @Ease() annotation |
|
| ease_generator | Code generator | |
| ease_devtools_extension | DevTools integration |
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:
- Install Ease State Helper extension from VS Code marketplace
- Right-click folder → Ease: New ViewModel
- 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.
Libraries
- ease_state_helper
- Simple Flutter state management helper.