riverpod_sugar 1.0.4
riverpod_sugar: ^1.0.4 copied to clipboard
Revolutionary ScreenUtil-style extensions for Riverpod. Simplify state management with concise one-liners and flexible widgets.
๐ฏ Riverpod Sugar #
The sweetest way to use Riverpod! A collection of lightweight widgets, utilities, and revolutionary ScreenUtil-style extensions that dramatically reduce boilerplate and improve developer ergonomics when using flutter_riverpod
.
๐ REVOLUTIONARY: ScreenUtil-Style One-Liners! #
Just like ScreenUtil made responsive design simple with .w
, .h
, .r
, .sp
- Riverpod Sugar makes state management simple with .state
, .text
, .toggle
!
Before vs After #
Traditional Riverpod (20+ lines for simple counter):
final counterProvider = StateProvider<int>((ref) => 0);
class CounterWidget extends ConsumerWidget {
const CounterWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Column(
children: [
Text('$count'),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Text('Increment'),
),
],
);
}
}
With Sugar Extensions (5 lines for same functionality):
final counter = 0.state; // ONE WORD!
class CounterWidget extends RxWidget {
Widget buildRx(context, ref) => Column(children: [
Text('${ref.watchValue(counter)}'), // Your design freedom!
ElevatedButton(onPressed: () => counter.increment(ref), child: Text('+')),
]);
}
โจ Complete Feature Set #
๐ฏ Core Features #
- ๐ฅ ScreenUtil-Style Extensions:
.state
,.text
,.toggle
,.items
- Create providers instantly! - โก RxWidget Family: Drop-in replacements for ConsumerWidget with cleaner syntax
- ๐ญ easyWhen: Simplified async state handling with default loading/error states
- ๐ FormManager: Complete form validation state management with built-in validators
- โฑ๏ธ Advanced Debouncer: Prevent rapid state updates with customizable strategies
- ๐ Provider Combiners: Combine multiple providers elegantly with intelligent error handling
- ๐งฉ Utility Widgets: RxBuilder, RxShow, and more for common patterns
๐ฅ Sugar Extensions - The Game Changer #
Create providers instantly with ScreenUtil-style syntax:
// Create providers in ONE WORD
final counter = 0.state; // StateProvider<int>
final name = "John".text; // StateProvider<String>
final isDark = false.toggle; // StateProvider<bool>
final todos = <String>[].items; // StateProvider<List<String>>
final price = 19.99.price; // StateProvider<double>
// Update state in ONE LINE with clear descriptive names
counter.increment(ref); // Increment by 1
counter.decrement(ref); // Decrement by 1
counter.addValue(ref, 5); // Add specific value
counter.resetToZero(ref); // Reset to 0
name.updateText(ref, "Jane"); // Update text
name.clearText(ref); // Clear text
name.appendText(ref, " Doe"); // Append text
isDark.toggle(ref); // Toggle boolean
isDark.setTrue(ref); // Set to true
isDark.setFalse(ref); // Set to false
todos.addItem(ref, "New task"); // Add item to list
todos.removeItem(ref, "Old task"); // Remove specific item
todos.clearAll(ref); // Clear entire list
// Display widgets in ONE LINE
ref.counter(counter); // Text widget showing count
ref.txt(name); // Text widget showing string
ref.show(isDark, MyWidget()); // Conditional widget
ref.stepper(counter); // +/- buttons with counter
๐ ๏ธ Enhanced Widget Helpers (v1.0.3+) #
Build UI components directly from providers with intelligent defaults:
// Text and display widgets
ref.text(counter, style: TextStyle(fontSize: 24)); // Text from any provider
ref.loading(isLoadingProvider, size: 24); // Loading indicator
// Interactive controls
ref.switchTile(isDarkMode, title: "Dark Mode", subtitle: "Toggle theme");
ref.checkboxTile(agreeTerms, title: "I agree to terms");
ref.slider(volume, min: 0, max: 100, divisions: 10);
ref.stepper(counter, step: 5, min: 0, max: 100);
// Visual components
ref.chip(statusProvider, backgroundColor: Colors.blue);
ref.card(myWidget, visible: showCardProvider, padding: EdgeInsets.all(16));
ref.animatedContainer(sizeProvider, duration: Duration(milliseconds: 300));
// All helpers include smart defaults, validation, and debugging support!
๐ Advanced Debugging & Validation (v1.0.3+) #
Production-ready debugging and validation tools:
// Automatic performance tracking (debug mode only)
SugarPerformance.track('my-operation', () {
counter.increment(ref);
});
// Safe operations with validation
listProvider.safeAddItem(ref, newItem, maxLength: 100);
textProvider.safeUpdateText(ref, newText, minLength: 3);
// Enhanced error messages with suggestions
try {
listProvider.removeAt(ref, invalidIndex);
} catch (e) {
// Gets helpful error: "Index 5 is out of bounds for list of length 3.
// Suggestion: Use safeRemoveAt() or check list length first."
}
๐ Quick Start #
1. Installation #
Add to your pubspec.yaml
:
dependencies:
riverpod_sugar: ^1.0.4
2. Import and Use #
import 'package:riverpod_sugar/riverpod_sugar.dart';
// Create providers instantly (ScreenUtil style!)
final counter = 0.state;
final name = "Anonymous".text;
final isDark = false.toggle;
// Use clean RxWidget syntax
class MyApp extends RxWidget {
@override
Widget buildRx(BuildContext context, WidgetRef ref) {
return Column(children: [
ref.counter(counter), // Show counter
ref.stepper(counter), // +/- buttons
ref.txt(name), // Show text
ref.show(isDark, Icon(Icons.dark_mode)), // Conditional widget
]);
}
}
3. That's it! ๐ #
You now have ultra-concise state management that's 80% less code than traditional Riverpod!
๐ Complete Documentation #
๐ฏ RxWidget Family - Clean Syntax #
Replace verbose ConsumerWidget with clean RxWidget:
Before (Standard Riverpod):
class CounterWidget extends ConsumerWidget {
const CounterWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('Count: $count');
}
}
After (With RxWidget):
class CounterWidget extends RxWidget {
@override
Widget buildRx(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('Count: $count');
}
}
Widget | Use Case |
---|---|
RxWidget |
Replaces ConsumerWidget with cleaner syntax |
RxStatefulWidget |
Replaces ConsumerStatefulWidget |
RxBuilder |
Inline reactive widgets without creating new classes |
RxShow |
Conditional rendering based on provider state |
๐ญ easyWhen - Simplified AsyncValue Handling #
Before (Standard Riverpod):
ref.watch(userProvider).when(
data: (user) => Text('Hello ${user.name}!'),
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('Error: $error'),
)
After (With easyWhen):
ref.watch(userProvider).easyWhen(
data: (user) => Text('Hello ${user.name}!'),
// loading & error widgets provided automatically!
)
Extension | Purpose |
---|---|
easyWhen() |
Simplified async state handling with defaults |
mapData() |
Transform data while preserving async state |
hasDataWhere() |
Check conditions on async data |
dataOrNull |
Get data or null safely |
๐ FormManager - Complete Form Validation #
final formManagerProvider = StateNotifierProvider<FormManager, FormState>((ref) {
return FormManager();
});
class RegistrationForm extends RxWidget {
@override
Widget buildRx(BuildContext context, WidgetRef ref) {
final formState = ref.watch(formManagerProvider);
final formManager = ref.read(formManagerProvider.notifier);
return Column(
children: [
TextFormField(
decoration: InputDecoration(
labelText: 'Email',
errorText: formState.getError('email'),
),
onChanged: (value) {
formManager.validateField(
'email',
value,
CommonValidators.combine([
CommonValidators.required('Email is required'),
CommonValidators.email('Please enter a valid email'),
]),
);
},
),
ElevatedButton(
onPressed: formState.isValid ? () => _submitForm() : null,
child: Text('Submit'),
),
],
);
}
}
Component | Purpose |
---|---|
FormManager |
Manages form validation state |
CommonValidators |
Pre-built validation functions (email, required, minLength, etc.) |
FormState |
Immutable form state with error tracking |
๐ Provider Combiners - Intelligent Combination #
final userProvider = FutureProvider<User>((ref) => fetchUser());
final settingsProvider = FutureProvider<Settings>((ref) => fetchSettings());
// Combine multiple providers with intelligent error/loading handling
final combinedProvider = ProviderCombiners.combine2(
userProvider,
settingsProvider,
);
// Use in widget
class UserDashboard extends RxWidget {
@override
Widget buildRx(BuildContext context, WidgetRef ref) {
final combined = ref.watch(combinedProvider);
return combined.easyWhen(
data: ((user, settings)) => DashboardContent(user, settings),
);
}
}
Combiner | Purpose |
---|---|
combine2/3/4() |
Combine multiple providers into tuples |
combineList() |
Combine list of same-type providers |
AsyncProviderCombiners |
Smart async provider combination |
map() |
Transform provider values |
โฑ๏ธ Advanced Debouncer - Smart Delay #
// Basic debouncing for search
final debouncer = Debouncer(milliseconds: 300);
onChanged: (query) => debouncer.run(() => search(query));
// Advanced debouncing with custom strategies
final advancedDebouncer = AdvancedDebouncer(
milliseconds: 300,
maxWait: 2000, // Force execution after 2 seconds max
leading: true, // Execute immediately on first call
trailing: true, // Execute after delay
);
Utility | Purpose |
---|---|
Debouncer |
Simple delay function execution |
AdvancedDebouncer |
Advanced with leading/trailing/maxWait options |
๐ฅ Revolutionary Sugar Extensions Reference #
Create Providers Instantly #
// Numbers
final counter = 0.state; // StateProvider<int>
final price = 19.99.price; // StateProvider<double>
// Strings
final name = "John".text; // StateProvider<String>
final query = "".search; // StateProvider<String>
// Booleans
final isDark = false.toggle; // StateProvider<bool>
final isLoading = false.loading; // StateProvider<bool>
final isVisible = true.visible; // StateProvider<bool>
// Lists
final todos = <String>[].items; // StateProvider<List<String>>
final tasks = <Task>[].todos; // StateProvider<List<Task>>
Update State in One Line #
// Counter operations with descriptive names
counter.increment(ref); // Increment by 1
counter.decrement(ref); // Decrement by 1
counter.addValue(ref, 5); // Add specific value
counter.resetToZero(ref); // Reset to 0
// Text operations with clear intent
name.updateText(ref, "Jane"); // Set new text
name.clearText(ref); // Clear text
name.appendText(ref, " Doe"); // Append text
// Boolean operations that are self-explanatory
isDark.toggle(ref); // Toggle state
isDark.setTrue(ref); // Set to true
isDark.setFalse(ref); // Set to false
// List operations with full clarity
todos.addItem(ref, "New task"); // Add item
todos.removeItem(ref, "Old task"); // Remove item
todos.clear(ref); // Clear all
// Generic operations (works with any StateProvider)
provider.set(ref, newValue); // Set value
provider.get(ref); // Get current value
provider.watch(ref); // Watch for changes
Build Widgets in One Line #
// Display widgets
ref.counter(counterProvider); // Text showing counter
ref.txt(nameProvider); // Text showing string
ref.show(boolProvider, MyWidget()); // Conditional widget
ref.stepper(counterProvider); // +/- buttons with counter display
ref.switchTile(boolProvider, title: "Dark Mode"); // Switch widget
๐จ Best Practices #
Widget Organization #
// โ
Excellent: Use Sugar extensions for instant providers
final counter = 0.state;
final name = "Guest".text;
final isDark = false.toggle;
// โ
Excellent: Use RxWidget for clean reactive widgets
class UserProfile extends RxWidget {
@override
Widget buildRx(BuildContext context, WidgetRef ref) {
return Column(children: [
ref.counter(counter),
ref.txt(name),
ref.show(isDark, Icon(Icons.dark_mode)),
]);
}
}
// โ
Good: Use RxBuilder for inline reactive parts
Column(
children: [
StaticHeader(),
RxBuilder(builder: (context, ref) {
return ref.counter(counter);
}),
],
)
Performance Tips #
// โ
Excellent: Use Sugar extensions for instant updates
counter.increment(ref); // Instead of ref.read(provider.notifier).state++
// โ
Good: Combine related providers
final dashboardProvider = ProviderCombiners.combine2(userProvider, settingsProvider);
// โ
Good: Use debouncer for expensive operations
final debouncer = Debouncer(milliseconds: 300);
onChanged: (query) => debouncer.run(() => search(query));
// โ Avoid: Multiple separate watches
// final user = ref.watch(userProvider);
// final settings = ref.watch(settingsProvider);
๐งช Testing #
Riverpod Sugar works seamlessly with Riverpod's testing utilities:
void main() {
test('Sugar extensions work in tests', () {
final container = ProviderContainer();
// Test sugar extensions
final counter = 0.state;
expect(counter.get(container.read), 0);
counter.increment(container.read);
expect(counter.get(container.read), 1);
container.dispose();
});
test('RxWidget updates when provider changes', () async {
final container = ProviderContainer();
await tester.pumpWidget(
ProviderScope(
parent: container,
child: MyRxWidget(),
),
);
// Verify behavior
expect(find.text('0'), findsOneWidget);
// Use sugar extension to update
counter.increment(container.read);
await tester.pump();
expect(find.text('1'), findsOneWidget);
});
}
๐ Migration Guide #
From Standard Riverpod to Sugar #
1. Provider Creation
// Before
final counterProvider = StateProvider<int>((ref) => 0);
final nameProvider = StateProvider<String>((ref) => "Guest");
// After - One word!
final counter = 0.state;
final name = "Guest".text;
2. State Updates
// Before
ref.read(counterProvider.notifier).state++;
ref.read(nameProvider.notifier).state = "New Name";
// After - One line with clear intent!
counter.increment(ref);
name.updateText(ref, "New Name");
3. Widget Building
// Before
class MyWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return Text('${ref.watch(counterProvider)}');
}
}
// After
class MyWidget extends RxWidget {
Widget buildRx(BuildContext context, WidgetRef ref) {
return ref.counter(counter); // Even shorter!
}
}
From ConsumerWidget to RxWidget #
-
Change the parent class:
// Before class MyWidget extends ConsumerWidget { // After class MyWidget extends RxWidget {
-
Rename the build method:
// Before Widget build(BuildContext context, WidgetRef ref) { // After Widget buildRx(BuildContext context, WidgetRef ref) {
-
That's it! Your existing code works unchanged.
๐ Coverage & Compatibility #
โ Perfect For (95% coverage) #
- Social Media Apps: Posts, likes, comments, user state
- E-commerce Apps: Cart, products, checkout, preferences
- Productivity/Todo Apps: Lists, completion, filters, settings
- Form-heavy Apps: Registration, surveys, validation
- Dashboard Apps: Data combination, async loading
- Settings/Config Apps: Toggles, preferences, themes
โ ๏ธ Good For (70% coverage) #
- Chat Apps: Basic messaging โ , real-time features need additional work
- Complex Business Apps: Forms/validation โ , advanced business logic needs custom implementation
โ Needs Additional Work #
- Real-time Trading: Streaming data, high-frequency updates
- Complex Games: Game-specific state patterns, performance-critical updates
- Enterprise Workflows: Advanced business rules, complex state machines
๐ What's New in v1.0.4? #
๏ฟฝ Package Optimization (v1.0.4) #
Dramatically Reduced Package Size:
- Package size reduced from ~13 MB to just 340 KB (97% reduction!)
- Removed build artifacts, coverage reports, and development files
- Much faster downloads and installation for users
- Professional, clean package structure with only essential files
Enhanced pub.dev Presence:
- Added topics for better discoverability
- Repository and issue tracker links
- Screenshot descriptions for visual documentation
- Optimized metadata for pub.dev rankings
๐ ๏ธ Foundation Improvements (v1.0.3) #
Enhanced Widget Helpers (15+ new helpers):
- Build UI components directly from providers:
ref.text()
,ref.switchTile()
,ref.slider()
- Smart defaults and validation for all helpers
- Zero boilerplate for common UI patterns
Advanced Debugging System:
- Automatic performance tracking in debug mode
- State change logging and operation monitoring
- Zero runtime overhead in release builds
Robust Validation Framework:
- Type-safe operations with bounds checking
- Context-aware error messages with suggestions
- Comprehensive validation utilities
Production-Ready Quality:
- 100% test coverage for all new features
- Complete documentation with examples
- Enhanced error handling and developer experience
Perfect for teams moving to production! Optimized package size with all the reliability and debugging tools you need for real-world apps.
๐ Why Choose Riverpod Sugar? #
๐ฅ Revolutionary Simplicity #
- 80% Less Code: Turn 50-line Riverpod apps into 10-line apps
- Zero Learning Curve: If you know ScreenUtil, you know this!
- Instant Productivity: Create providers and update state in one word
๐ฏ Production Ready #
- Full Riverpod Compatibility: Built on top of flutter_riverpod
- Comprehensive Testing: Extensive test coverage
- Type Safe: Full null safety and type inference
- Performance Optimized: No overhead, pure convenience
๐ Developer Experience #
- Intuitive API: Natural, readable code
- Excellent Documentation: Examples for everything
- Active Maintenance: Regular updates and improvements
- Community Driven: Built for the Flutter community
๐ค Contributing #
We welcome contributions! Please see our Contributing Guide for details.
๐ License #
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Acknowledgments #
- Built on top of the excellent flutter_riverpod package
- Inspired by ScreenUtil's revolutionary approach to responsive design
- Thanks to the Flutter community for feedback and contributions
Made with โค๏ธ for the Flutter community
Transform your Riverpod experience today - because state management should be sweet, not complex! ๐ฏ