Reactive Architecture
The philosophy behind Fluxy's reactivity and state management.
Reactive Architecture
The core philosophy of Fluxy is to minimize re-renders and make data flow explicit.
The Reactive Graph
Fluxy uses a Signal Graph (Flux System) to track dependencies. Every Flux, FluxComputed, and FluxEffect is a node in this graph.
This is different from traditional setState() which rebuilds entire widget subtrees.
When a Flux updates, it notifies only its direct dependents. If a FluxComputed value depends on that signal, it recalculates. If a FluxEffect uses that signal, it re-runs. This ensures that only the necessary parts of your UI update.
Visualizing the Flow
graph TD
A[User Action (Tap)] -->|Triggers| B[FluxController Method]
B -->|Updates| C[Flux<T> Signal]
C -->|Notifies| D{Reactive Graph}
D -->|Rebuilds| E[Fx Widget (UI)]
E -->|Renders| F[Flutter Engine]
subgraph "Logic Layer"
B
C
end
subgraph "View Layer"
E
A
endArchitectural Authority
Fluxy v0.1.9 formalizes the Architectural Authority of the framework, moving from a library to an application platform. It enforces clean separation of concerns through standardized base classes:
- Logic Layer (
FluxController): Handles business logic with native lifecycle hooks (onInit,onReady,onDispose). - Data Layer (
FluxRepository): Manages offline-first data orchestration with automatic Local/Remote synchronization. - Extension Layer (
FluxyPlugin): Modular interface for building framework extensions that hook into the global lifecycle (onRegister,onAppReady,onDispose). - Feature-First Structure: Enforces a
lib/featuresandlib/coredirectory convention for scalable, enterprise-grade development.
Single Engine Philosophy
Instead of relying on multiple disjointed state management solutions, navigation libraries, and networking engines, everything in Fluxy is unified under a single Engine instance. This engine coordinates:
- State Updates: Automatic batching and dependency tracking.
- Networking: Native
FluxyHttpengine with global interceptors. - Routing: Managing the navigation stack and history with automatic controller factory.
- Theming: Applying global styles and responsive breakpoints.
- OTA Updates: Handling remote manifest downloads and hot-swaps.
This unification reduces complexity and ensures that all parts of your application work together seamlessly.
Performance First
Performance is built-in, not an afterthought.
- Fine-Grained Updates: Only change the specific text node or style property that needs to update.
- Lazy Evaluation:
Computedvalues are only calculated when accessed. - Zero Overhead: While the developer experience is rich, the runtime cost is minimal.
Flat Widget Tree (Attribute Accumulation)
Fluxy v0.1.6 optimizes UI performance through a Flat Widget Tree. Unlike traditional modifier-based frameworks that wrap widgets in nested layers (Padding > DecoratedBox > Opacity), Fluxy uses an Attribute Accumulation pattern.
How it Works
When you chain modifiers:
Fx.box()
.p(16)
.bg(Colors.blue)
.rounded(12)
.shadow.lgThe engine accumulates these attributes into a single internal style object. This results in far fewer widgets in the element tree, leading to better memory usage and faster layout passes.
graph LR
subgraph "Traditional Approach"
A[Padding] --> B[DecoratedBox]
B --> C[Opacity]
C --> D[Child]
end
subgraph "Fluxy Approach"
E[FxBox (With Styles)] --> F[Child]
endThe Master Feature Structure
Fluxy enforces a "Feature-First" architecture. Instead of putting all controllers in one folder and all views in another, we group them by business utility. Here is a complete representation of a Product Catalog feature.
Folder Structure
lib/
└── features/
└── catalog/
├── catalog.controller.dart # Business Logic
├── catalog.repository.dart # Data Orchestration
├── catalog.view.dart # UI Layer
└── widgets/ # Child components
└── product_card.dartImplementation Example
// 1. DATA LAYER: catalog.repository.dart
class CatalogRepository extends FluxRepository {
// Offline-first storage automatically managed
late final products = flux([], persistKey: 'cached_products');
Future<void> refresh() async {
final data = await Fx.http.get('/api/products');
products.value = data; // Triggers UI across the whole app
}
}
// 2. LOGIC LAYER: catalog.controller.dart
class CatalogController extends FluxController {
final repo = use<CatalogRepository>();
final searchQuery = flux('', label: 'catalog_search');
// Computed: Real-time filtering with 0 listener boilerplate
late final filteredProducts = fluxComputed(() {
if (searchQuery.value.isEmpty) return repo.products.value;
return repo.products.value.where((p) => p.name.contains(searchQuery.value)).toList();
});
void onProductTap(Product p) => Fx.platform.haptics.heavy();
}
// 3. UI LAYER: catalog.view.dart
class CatalogView extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Controller is automatically found in the registry
final logic = use<CatalogController>();
return Fx.layout(
// Responsive layout: Mobile vs Tablet/Desktop
mobile: Fx.col(
children: [
Fx.field(logic.searchQuery).placeholder("Search..."),
Fx(() => Fx.col(
children: logic.filteredProducts.value.map((p) => ProductCard(p)).toList(),
)).scrollable(),
],
),
);
}
}This structural pattern ensures that your code is modular, testable, and scales to hundreds of features without becoming a "Spaghetti" codebase.
Data vs Services (The Toolbelt vs The Manager)
In Fluxy, we distinguish between Repositories (Data Layer) and Services (Infrastructure Layer).
| Concept | Role | Example |
|---|---|---|
| Repository | The Manager: Owns feature-specific data (Products, Users). | catalog.repository.dart |
| Service | The Tool: Provides global capabilities (Camera, Logs, Auth). | auth.service.dart |
- Rule of Thumb: If it manages your business models, it's a Repository. If it provides a generic hardware or utility function, it's a Service.
Routing Strategy (Centralized vs Isolated)
Fluxy does not mandate a specific location for routes. Choose the strategy that matches your team size.
1. Centralized Strategy (Small Teams)
Keep a single lib/core/routes.dart file. It's easier to see the entire application flow in one place.
2. Isolated Strategy (Enterprise Teams)
Each feature folder owns a feature.route.dart file. This prevents merge conflicts when 10+ developers are working on different modules simultaneously.
[!TIP] Start with a Centralized route file. Only move to Isolated routes when your feature folder becomes complex or your team grows.