ease_state_helper 0.3.0+1
ease_state_helper: ^0.3.0+1 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.
Motivation #
Flutter's InheritedWidget and InheritedModel are powerful but boilerplate-heavy. Ease provides a modern, Riverpod-like Developer Experience (DX) directly on top of these native primitives.
It aims to be the "Standard Library" extension for Flutter state management—zero extra dependencies, native performance, and type safety out of the box.
The Model #
If you've used Riverpod or Provider, Ease will feel familiar:
// context.counterViewModel => Watch (rebuilds widget)
// context.readCounterViewModel() => Read (no rebuild, for callbacks)
// context.selectCounterViewModel => Select (rebuilds only on specific field change)
Table of Contents #
- Motivation
- Getting Started
- Core Concepts
- API Reference
- Advanced Usage
- DevTools Support
- Packages
- Examples
- Community & Support
- Contributing
Getting Started #
1. Installation #
Add the latest version to your pubspec.yaml:
dependencies:
ease_state_helper: ^0.3.0
ease_annotation: ^0.2.0
dev_dependencies:
ease_generator: ^0.3.0
build_runner: ^2.4.0
2. 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++;
}
3. Generate & Register #
dart run build_runner build
void main() {
runApp(
EaseScope(
providers: [(child) => CounterViewModelProvider(child: child)],
child: const MyApp(),
),
);
}
4. Use #
Widget build(BuildContext context) {
final counter = context.counterViewModel;
return Text('${counter.state}');
}
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 - auto-notifies
void clear() => state = const CartState();
// Named action - better DevTools visibility
void addItem(Product product) {
setState(
state.copyWith(items: [...state.items, product]),
action: 'addItem',
);
}
// Functional update
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),
);
Context-Safe Listeners (Side Effects) #
Use listenOnYourViewModel for context-aware side effects (snackbars, navigation). It automatically handles unmounting and cleanup.
@override
void initState() {
super.initState();
context.listenOnCartViewModel((prev, next) {
if (next.error != null && prev.error != next.error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(next.error!)),
);
}
});
}
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 (Coming Soon to pub.dev) #
Currently, you can use the DevTools extension by adding it as a git dependency:
dev_dependencies:
ease_devtools_extension:
git:
url: https://github.com/y3l1n4ung/ease.git
path: packages/ease_devtools_extension
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 | Status |
|---|---|---|
| ease_state_helper | Core runtime library | |
| ease_annotation | @Ease() annotation |
|
| ease_generator | Code generator | |
| ease_devtools_extension | DevTools integration | ⏳ Coming Soon |
Project Structure #
Ease is maintained as a monorepo using Melos.
| Path | Package | Description |
|---|---|---|
packages/ease_state_helper |
ease_state_helper | Core runtime library. |
packages/ease_annotation |
ease_annotation | Annotations for code generation. |
packages/ease_generator |
ease_generator | build_runner based code generator. |
packages/ease_devtools_extension |
ease_devtools_extension | Flutter DevTools extension. |
apps/example |
- | General examples and integration tests. |
apps/shopping_app |
- | Real-world example application. |
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
Community & Support #
- Need help? Check out our Support Guide.
- Found a bug? Open an issue.
- Want to chat? Join our Discord.
- Code of Conduct: Please read and follow our Code of Conduct.
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.