State Management
The Fluxy State System - Ultra-scale reactive state management.
State Management
Fluxy v1.0.0 provides the Fluxy State System - a comprehensive reactive state management engine that provides enterprise-grade features with modular architecture support.
The Fluxy State Units
Fluxy uses its own branded reactive state management system:
| Concept | Fluxy Unit | Description |
|---|---|---|
| Core State | Flux<T> | Reactive signal for primitive and complex data |
| Derived State | FluxComputed<T> | Computed values that automatically update |
| Side Effects | FluxEffect | Reactive side effects and async operations |
| Async State | AsyncFlux<T> | Built-in async state handling |
| Collections | FluxList, FluxMap | Reactive collections |
| Persistence | flux<T>(..., persist: true) | Built-in state persistence |
Basic Usage (v1.0.0)
import 'package:fluxy/fluxy.dart';
// Create a reactive signal
final count = flux(0);
// Create a computed signal
final isDouble = fluxComputed(() => count.value * 2);
// Use it in UI (atomic reactivity)
Fx(() => Fx.text("Count is: ${count.value}"))Note: The flux function creates Flux<T> objects that automatically update UI when values change. This is the core of Fluxy's reactivity system.
Advanced Features
Persistence
Fluxy v1.0.0 includes built-in state persistence with encryption:
// Persistent state (automatically saved/loaded)
final session = flux<User?>(null, key: 'auth_session', persist: true);
// Usage in UI
Fx(() => Fx.text("Welcome, ${session.value?.name ?? 'Guest'}"))Selectors
Solves the "Riverpod Rebuild" problem by allowing you to listen to tiny parts of large objects.
final userFlux = flux(User(name: "John", age: 30));
final userName = fluxSelector(userFlux, (u) => u.name);
// userName widget only rebuilds when name changes, not age!
Fx(() => Fx.text("Name: ${userName.value}"))Benefit: If userFlux.age changes, the userName widget will not rebuild. 100% atomic.
3. asyncFlux (Server State)
Simplify API calls and async operations with built-in loading/error states:
// Automatic async state management
final users = asyncFlux(() => Fx.http.get('/api/users'));
// UI automatically handles loading/error/data states
Fx(() => users.on(
data: (list) => UserList(users: list),
loading: () => Fx.loader.shimmer(),
error: (e) => ErrorView(e),
))4. fluxLocal (Component State)
Scoped state that automatically disposes with the widget using FluxyLocalMixin:
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> with FluxyLocalMixin {
@override
Widget build(BuildContext context) {
// Local state - automatically disposed when widget is removed
final localCount = fluxLocal(0);
return Fx.button("Count: ${localCount.value}")
.onTap(() => localCount.value++);
}
}5. fluxEffect (Side Effects)
Handle side effects when state changes:
final count = flux(0);
fluxEffect(() {
print('Count changed: ${count.value}');
// Perform side effects here
}, [count]);Modular Architecture Integration
With v1.0.0, state management works seamlessly with modular packages:
import 'package:fluxy/fluxy.dart';
import 'package:fluxy_auth/fluxy_auth.dart';
import 'package:fluxy_storage/fluxy_storage.dart';
class AppController extends FluxController {
// Core state management
final isLoading = flux(false);
final error = flux<String?>(null);
// Modular state integration
final authState = fluxSelector(Fx.platform.auth.state, (s) => s);
final userPrefs = flux<Map<String, dynamic>>({});
@override
void onInit() {
super.onInit();
_loadUserData();
}
Future<void> _loadUserData() async {
isLoading.value = true;
try {
// Use modular storage
final prefs = await Fx.platform.storage.getAll();
userPrefs.value = prefs;
} catch (e) {
error.value = e.toString();
} finally {
isLoading.value = false;
}
}
}Professional Logging (v0.2.6+)
Fluxy includes professional logging for state operations:
// Logs will show:
// [DATA] [AUDIT] Flux created: count
// [DATA] [AUDIT] Flux updated: count = 5
// [DATA] [REPAIR] Auto-repair applied to circular dependency
final count = flux(0);
count.value = 5; // [DATA] [AUDIT] Flux updated: count = 5Performance Features
1. Batch Updates
Fluxy automatically batches multiple state updates:
// These updates are batched into a single rebuild
count.value = 1;
name.value = "John";
isLoading.value = false;2. Dependency Tracking
Only rebuild widgets that actually use the changed state:
// Only rebuilds when count changes
Fx(() => Fx.text("Count: $count"))
// Only rebuilds when name changes
Fx(() => Fx.text("Name: $name"))3. Memory Management
Automatic cleanup and weak references prevent memory leaks:
// Automatically disposed when no longer referenced
final tempState = flux(temporaryData);2. fluxWorker (Zero-Jank Computations)
Never freeze your UI again. Automatically moves expensive work to a background Dart Isolate.
// Runs in background isolate
final processedData = fluxWorker(heavyTask, data);
// UI stays responsive
Fx(() => heavyResult.when(
loading: () => Fx.text("Processing..."),
data: (d) => Fx.text("Result: $d"),
error: (e) => Fx.text("Error: $e"),
))Perfect for: Searching large lists, parsing complex JSON, image processing.
2.5 fluxIsolate (Task Isolation)
A high-level wrapper for executing one-off heavy tasks in a separate isolate without setting up a continuous worker.
final result = await fluxIsolate(() {
// Expensive synchronous computation
return crunchNumbers(data);
});3. FluxyLocalMixin (Auto-Cleaning State)
Prevents memory leaks by making state "local" to a widget with automatic cleanup.
class _MyState extends State<MyWidget> with FluxyLocalMixin {
late final localCount = fluxLocal(0); // Auto-disposed!
late final localComputed = fluxLocalComputed(() => localCount.value * 2);
late final localWorker = fluxLocalWorker(_expensiveTask, data);
@override
Widget build(BuildContext context) {
return Fx(() => Fx.button("Count: ${localCount.value}")
.onPressed(() => localCount.value++));
}
}Offline-First Architecture
FluxyRepository for Multi-User Apps
Specialized helpers for multi-user scoping and database stream binding.
class ChatRepository extends FluxyRepository {
// 1. Scoped Persistence (Prevents data leakage between users)
late final messages = flux([],
persistKey: userScope(auth.userId, 'chat_history')
);
// 2. Database Subscription (Auto-cleans up on dispose)
void init() {
bindStream(database.watchMessages(), messages);
}
}State Hydration
Automatic state persistence and restoration across app restarts.
void main() async {
await Fluxy.init(); // Loads ALL saved state from disk!
runApp(MyApp());
}
// Any flux with persistKey is automatically hydrated
final theme = flux("dark", persistKey: "app_theme");
final userPrefs = flux({}, persistKey: "user_preferences");Performance Optimizations
.peek() - Read Without Listening
Read a value without creating a reactive dependency.
// For logging or non-reactive logic
print(count.peek()); // Won't trigger rebuild if count changesuntracked() - Zero Dependency Blocks
Run code blocks without tracking dependencies.
untracked(() {
// Read multiple fluxes without creating circular dependencies
print(count.value);
print(name.value);
});batch() - Group Updates
Prevent intermediate rebuilds when making multiple changes.
batch(() {
count.value++;
name.value = "New Name";
// Subscribing widgets only rebuild once
});Global Middleware System
Intercept every state change across your application for analytics, logging, or state-guarding.
// Create a middleware
class LoggerMiddleware extends FluxyMiddleware {
@override
void onUpdate(String key, dynamic oldValue, dynamic newValue) {
print('[FLUX UPDATE] $key | $oldValue -> $newValue');
}
}
// Register globally
void main() async {
Fluxy.use(LoggerMiddleware());
await Fluxy.init();
runApp(MyApp());
}Time Travel (Undo/Redo)
Built-in history management for any flux.
final textHistory = fluxHistory("Hello");
// Push new state
textHistory.value = "Rocks";
// Undo/Redo
textHistory.undo(); // Back to "Hello"
textHistory.redo(); // Forward to "Rocks"
// Reactive buttons
Fx(() => Fx.button("Undo")
.onPressed(textHistory.canUndo ? textHistory.undo : null)
.opacity(textHistory.canUndo ? 1.0 : 0.5))Async State Management
Enhanced async state with smart error handling and loading states.
final apiResult = fluxAsync(() => fetchData());
// Functional state handling
Fx(() => apiResult.when(
loading: () => Fx.loader(),
data: (data) => Fx.text("Data: $data"),
error: (error) => Fx.text("Error: $error").color(FxTokens.error),
))
// Or using the simplified on() method
Fx(() => apiResult.on(
data: (d) => Fx.text(d),
loading: () => Fx.loader(),
error: (e) => Fx.text("Error: $e"),
))Dependency Injection
Fluxy includes a robust, scope-aware DI system for managing application logic and service lifecycles.
Scoped Lifecycles (FxScope)
v0.2.0 introduces structured scopes to manage memory automatically and prevent leaks.
| Scope | Lifecycle | Best For |
|---|---|---|
FxScope.app | Lives for the entire app duration. | Auth, Theme, Settings. |
FxScope.route | Disposed when the parent route is popped. | Feature Controllers, Page Logic. |
FxScope.factory | Creates a new instance on every request. | Transient utilities, DTOs. |
Basic Usage
// Register services with specific scopes
FluxyDI.put(AuthController(), scope: FxScope.app);
FluxyDI.put(ProfileController(), scope: FxScope.route);
// Lazy loading (instance created only when first used)
FluxyDI.lazyPut(() => ExpensiveController()); Retrieving Dependencies
// Standard retrieval
final auth = FluxyDI.find<AuthController>();
// Using shorthands
final profile = use<ProfileController>();Automatic Route Cleanup
When using FxScope.route, Fluxy automatically identifies the active route context. When that route is removed from the navigation stack, the dependency is disposed and its onClose() method is called.
Circular Dependency Detection
The v0.2.0 DI engine includes a proactive tracker that blocks infinite resolution loops. If ServiceA requires ServiceB and vice-versa, Fluxy will throw a descriptive FluxyDIException during initialization instead of hanging the application.
Controllers with Lifecycle
FluxController provides native lifecycle hooks that are automatically managed by the Fluxy engine. This is where you should place your business logic.
class UserController extends FluxController {
final user = flux<User?>(null);
@override
void onInit() {
// Called when the controller is first initialized.
// Perfect for setting up listeners or initial data fetching.
super.onInit();
print("User Controller Initialized");
}
@override
void onReady() {
// Called after the first frame is rendered.
// Safe to show snackbars, dialogs, or perform navigation.
super.onReady();
}
@override
void onClose() {
// Called when the controller is removed from memory.
// Use for cleaning up streams, timers, or manual resources.
super.onClose();
}
}Architectural Scaffolding
Fluxy v0.1.9 introduces Blueprints and the FluxRepository pattern to standardize enterprise application development.
FluxRepository
Standardized data layer that handles offline-first orchestration.
class AuthRepository extends FluxRepository {
late final session = flux(null, persistKey: 'user_session');
Future<void> login(String email, String password) async {
final data = await Fx.http.post('/auth/login', body: {'email': email, 'password': password});
session.value = data;
}
}Scoped State
Restrict state to specific widget subtrees.
FluxyProvider(
create: (context) => MyController(),
child: ChildWidget(),
);Advanced Features
Numeric Operators
Automatic computed signals for arithmetic operations.
final a = flux(10);
final b = flux(5);
final sum = a + b; // Automatically a `FluxComputed<num>`Error Handling
Derived fluxes with error recovery.
final result = fluxComputed(
() => throw Exception("Failed"),
onError: (error, stack) => print("Log error: $error"),
);Local State
For state that only lives within a single widget.
class _MyWidgetState extends State<MyWidget> {
final count = flux(0); // Auto-disposed with widget
@override
Widget build(BuildContext context) {
return Fx(() => Fx.button("Count: ${count.value}")
.onPressed(() => count.value++));
}
}Backward Compatibility
All existing Signal code continues to work without modification:
// This still works perfectly
Signal<String> count = flux("hello");
Computed<int> length = computed(() => count.value.length);Legacy aliases are marked with @Deprecated to guide developers toward the new branded API while maintaining compatibility.
The Master State Implementation
In a professional application, you don't just use single signals. You build a Unified Reactive Store. Here is how you tie everything together in a real-world project.
class UserProfileStore extends FluxController {
// 1. Core Reactive Data (With Labels for Snapshots)
final profile = flux<User>(User.guest(), label: 'user_profile');
final theme = flux('dark', label: 'app_theme', persist: true);
// 2. Computed Logic (Derived from multiple signals)
late final displayName = fluxComputed(() {
return profile.value.isGuest ? "Welcome Guest" : "Member: ${profile.value.name}";
});
// 3. Selection (Subscribe only to specific properties)
late final avatarUrl = fluxSelector(profile, (p) => p.photoUrl);
// 4. Async Data Loading
late final activityStats = asyncFlux(() => Fx.http.get('/api/stats'));
// 5. History (Undo / Redo for Settings)
final bio = fluxHistory("Write something...", label: 'user_bio');
@override
void onInit() {
super.onInit();
// 6. Side Effects
fluxEffect(() {
print("System Alert: Theme changed to ${theme.value}");
Fx.toast.info("Applying ${theme.value} mode");
});
}
}
// 7. Standardized UI Usage
class ProfileView extends StatelessWidget {
final store = use<UserProfileStore>();
@override
Widget build(BuildContext context) {
return Fx.col(
gap: 16,
children: [
// Atomic UI - only this text rebuilds on name change
Fx(() => Fx.text(store.displayName.value).font.bold()),
// Complex Async State
Fx(() => store.activityStats.on(
loading: () => Fx.shimmer(child: Fx.box().h(100)),
data: (stats) => Fx.text("Recent Activity: $stats"),
error: (e) => Fx.text("Stats unavailable").color(Colors.red),
)),
// Persistence in Action
Fx.button("Toggle Theme", onTap: () {
store.theme.update((t) => t == 'dark' ? 'light' : 'dark');
}),
],
);
}
}Fluxy's state management system provides enterprise-grade features including background processing, automatic cleanup, and global middleware - making it ready for production-scale applications.