Fluxy
Fundamentals

Reactivity

Fluxy State System - Flux, FluxComputed, and FluxEffect. The core of Fluxy.

Reactivity

Fluxy's reactivity is built on three main primitives: Flux, FluxComputed, and FluxEffect.

Flux (flux)

flux creates a mutable signal. Flux units are containers for values that can change over time.

final count = flux(0, label: "Counter"); // Best practice: add labels

// Set value
count.value = 5;

// Read value (creates a dependency)
print(count.value);

When you read a signal inside a reactive context (like an effect or a Fx widget builder), that context automatically subscribes to the signal.

[!TIP] Use Labels for Debugging: Giving your flux a label is highly recommended. Not only does it make the signal easily readable in the Fluxy DevTools, but labels are also required if you want to capture the value of the flux in a DevTools State Snapshot.

FluxComputed

fluxComputed creates a derived signal that depends on other flux units.

final count = flux(0);
final doubleCount = fluxComputed(() => count.value * 2);

doubleCount will automatically update whenever count updates. It is lazily evaluated and cached. It will only recompute when its value is accessed or when its dependencies change.

FluxEffect

fluxEffect runs a function immediately and re-runs it whenever any of its dependencies change.

fluxEffect(() {
  print("Count is now: ${count.value}");
});

This is useful for side effects like logging, manual DOM updates, or triggering network requests.

Reactivity in UI

To make your UI reactive, you have two primary options:

The Fx Factory

Wrap any widget builder in Fx(() => ...) to automatically subscribe to any signals used inside. This triggers atomic rebuilds only for that specific widget.

final count = flux(0);

// Only this Text widget rebuilds when count changes
Fx(() => Text("Count: ${count.value}")) 

Reactive Text Shorthand

For simple text display, use Fx.text(() => signal.value).

Fx.text(() => "Current: ${count.value}");

Two-Way Binding

Fluxy v0.1.4 provides native two-way binding for interactive components. Instead of manually handling onChanged and controller.text, you can bind a signal directly.

FxTextField

Binds its text content to a Signal<String>.

final username = flux("");

FxTextField(
  label: "Username",
  value: username, // Two-way binding
)

FxDropdown

Binds its selection to a Signal<T>.

final theme = flux("Light");

FxDropdown<String>(
  value: theme, // Two-way binding
  options: ["Light", "Dark", "System"],
)

Automatic Cleanup

Effects and subscriptions created within a widget's lifecycle are automatically disposed of when the widget is destroyed, preventing memory leaks.


The Master Reactive Data Flow Implementation

This example demonstrates how flux, fluxComputed, fluxEffect, and Two-Way Binding come together to create a complex, reactive feature without a single setState, TextEditingController, or StatefulWidget.

import 'package:flutter/material.dart';
import 'package:fluxy/fluxy.dart';

// 1. Define the reactive state (The "Store")
class CheckoutState {
  static final cartValue = flux(150.0, label: "Cart_Total");
  static final discountCode = flux("", label: "Checkout_PromoCode");
  
  // 2. Computed values automatically recalculate when dependencies change
  static final discountAmount = fluxComputed(() {
    final code = discountCode.value.toUpperCase();
    if (code == "SUMMER20") return 20.0;
    if (code == "FLUX50") return 50.0;
    return 0.0;
  }, label: "Checkout_Discount");
  
  static final finalTotal = fluxComputed(() {
    // Both cartValue and discountAmount are dependencies
    return cartValue.value - discountAmount.value;
  }, label: "Checkout_FinalTotal");
  
  static final isValidPromo = fluxComputed(() {
    return discountAmount.value > 0;
  });
}

class CheckoutScreen extends StatelessWidget {
  const CheckoutScreen({super.key});

  @override
  Widget build(BuildContext context) {
    // 3. Setup side effects mapping to analytics or logging
    fluxEffect(() {
       // This runs immediately, and then every time discountCode changes
       if (CheckoutState.isValidPromo.value) {
         Fx.toast.success("Promo Applied: \$${CheckoutState.discountAmount.value} off!");
         // AnalyticsService.track("promo_applied", CheckoutState.discountCode.value);
       }
    });

    return Fx.scaffold(
      appBar: Fx.appBar(title: "Secure Checkout"),
      // 4. Wrap with Fx() to automatically react to signal reads
      body: Fx(() => Fx.col(
        gap: 24,
        children: [
          // Basic Read
          Fx.text("Subtotal: \$${CheckoutState.cartValue.value}").h3(),
          
          // 5. Two-Way Binding directly to the signal
          Fx.input(
            label: "Promo Code",
            value: CheckoutState.discountCode, // No controller needed!
          ),
          
          if (CheckoutState.isValidPromo.value)
            Fx.text("Discount Applied: -\$${CheckoutState.discountAmount.value}")
              .color(Colors.green)
              .bold(),
              
          // Computed Read
          Fx.text("Total: \$${CheckoutState.finalTotal.value}").h1(),
          
          Fx.button("Complete Order")
            .primary()
            .onTap(() => Fx.toast.success("Order Placed!"))
            .wFlexible(), // Expand to fill width
        ],
      ).p(24)),
    );
  }
}

Notice how clean the CheckoutScreen is. There is no StatefulWidget, no build method clutter, and no manual controller disposal. Fluxy handles the entire reactive dependency graph completely "hands-free".

On this page