Computed

Computed Signals#

Computed signals derive their value from other signals and automatically update when dependencies change.

Creating Computed Signals#

final firstName = signal('John');
final lastName = signal('Doe');

final fullName = computed(() => '${firstName()} ${lastName()}');

print(fullName()); // "John Doe"

firstName.set('Jane');
print(fullName()); // "Jane Doe" - auto updated!

Automatic Dependency Tracking#

Computed signals automatically track which signals they read:

final a = signal(1);
final b = signal(2);
final c = signal(3);

final sum = computed(() => a() + b());
// sum depends on a and b, but NOT c

a.set(10); // sum recomputes
b.set(20); // sum recomputes
c.set(30); // sum does NOT recompute

Chaining Computed Signals#

Computed signals can depend on other computed signals:

final price = signal(100);
final quantity = signal(2);
final taxRate = signal(0.1);

final subtotal = computed(() => price() * quantity());
final tax = computed(() => subtotal() * taxRate());
final total = computed(() => subtotal() + tax());

print(total()); // 220

quantity.set(3);
print(total()); // 330 - all computed values updated

Conditional Dependencies#

Dependencies are tracked dynamically based on what's actually read:

final useMetric = signal(true);
final meters = signal(100);
final feet = signal(328);

final distance = computed(() {
  if (useMetric()) {
    return '${meters()} meters';
  } else {
    return '${feet()} feet';
  }
});

print(distance()); // "100 meters"
// Only meters is a dependency right now

useMetric.set(false);
print(distance()); // "328 feet"
// Now only feet is a dependency

Read-Only#

Computed signals are read-only:

final doubled = computed(() => count() * 2);

doubled.set(10); // Throws UnsupportedError!
doubled.update((v) => v + 1); // Throws UnsupportedError!

Listening to Computed#

You can listen to computed signals just like regular signals:

final count = signal(0);
final doubled = computed(() => count() * 2);

doubled.listen((value) {
  print('Doubled: $value');
});

count.set(5); // Prints: "Doubled: 10"

Memoization#

Computed signals cache their value and only recompute when dependencies change:

var computeCount = 0;

final expensive = computed(() {
  computeCount++;
  return heavyCalculation(input());
});

// First read - computes
print(expensive());
print(computeCount); // 1

// Second read - cached
print(expensive());
print(computeCount); // 1 (no recompute)

// Dependency changed - recomputes
input.set(newValue);
print(expensive());
print(computeCount); // 2

Best Practices#

Keep Computations Pure#

Computed functions should be pure (no side effects):

// Good
final total = computed(() => items().fold(0, (sum, i) => sum + i.price));

// Avoid
final total = computed(() {
  print('Computing total...'); // Side effect!
  return items().fold(0, (sum, i) => sum + i.price);
});

Use Effects for Side Effects#

When you need side effects, use effect():

effect(() {
  print('Total: ${total()}');
});

Avoid Heavy Computations#

If a computation is expensive, consider using untracked() for parts that don't need tracking:

final result = computed(() {
  final config = untracked(() => expensiveConfig());
  return process(data(), config);
});