Walkthroughs
Searchable List
This example combines a query signal, a reactive list, and a computed filter.
dart
final query = signal(context, '');
final items = ReactiveList.scoped(context, [
'Aurora',
'Comet',
'Nebula',
'Orion',
'Pulsar',
]);
final filtered = computed<List<String>>(context, (_) {
final q = query().trim().toLowerCase();
if (q.isEmpty) return List.unmodifiable(items);
return items.where((item) => item.toLowerCase().contains(q)).toList();
});UI wiring:
dart
TextField(
decoration: const InputDecoration(labelText: 'Search'),
onChanged: query.set,
);
SignalBuilder(
builder: (context) {
final results = filtered();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Showing ${results.length} items'),
for (final item in results) Text(item),
],
);
},
);Try it in the example app and watch the list update instantly.
Checkout Total + Autosave
This workflow shows .set(...), computed, and effect working together.
dart
final qty = signal(context, 1);
final unitPrice = signal(context, 19.0);
final discount = signal(context, 0.0); // 0%..30%
final subtotal = computed<double>(context, (_) => qty() * unitPrice());
final total = computed<double>(context, (_) => subtotal() * (1 - discount()));
final saves = signal(context, 0);
effect(context, () {
total(); // track computed
final current = untrack(() => saves());
saves.set(current + 1); // autosave counter
});Update inputs using .set(...):
dart
qty.set(qty() + 1);
discount.set(0.1); // 10% promo
unitPrice.set(24.0);Flutter Examples (from the example app)
dart
class WalkthroughSection extends StatelessWidget {
const WalkthroughSection({super.key});
@override
Widget build(BuildContext context) {
final query = signal<String>(context, '');
final items = ReactiveList.scoped(context, [
'Aurora',
'Comet',
'Nebula',
'Orion',
'Pulsar',
]);
final nextId = signal<int>(context, 1);
final controller = useMemoized(context, () => TextEditingController());
effect(context, () {
onEffectDispose(controller.dispose);
});
final filtered = computed<List<String>>(context, (_) {
final q = query().trim().toLowerCase();
if (q.isEmpty) return List.unmodifiable(items);
return items.where((item) => item.toLowerCase().contains(q)).toList();
});
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
key: const Key('walkthrough-query'),
controller: controller,
decoration: InputDecoration(
labelText: 'Search',
suffixIcon: IconButton(
tooltip: 'Clear filter',
onPressed: () {
controller.clear();
query.set('');
},
icon: const Icon(Icons.clear),
),
),
onChanged: query.set,
),
const SizedBox(height: 8),
SignalBuilder(
builder: (context) {
final results = filtered();
final total = items.length;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 8,
children: [
ElevatedButton(
onPressed: () {
items.add('Nova ${nextId()}');
nextId.set(nextId() + 1);
},
child: const Text('Add item'),
),
OutlinedButton(
onPressed: total == 0 ? null : () => items.removeLast(),
child: const Text('Remove last'),
),
TextButton(
onPressed: () {
controller.clear();
query.set('');
},
child: const Text('Clear filter'),
),
],
),
const SizedBox(height: 8),
Text('showing ${results.length} of $total'),
const SizedBox(height: 4),
for (final item in results) Text(item),
],
);
},
),
],
);
}
}dart
class CheckoutWorkflowSection extends StatelessWidget {
const CheckoutWorkflowSection({super.key});
@override
Widget build(BuildContext context) {
final qty = signal<int>(context, 1);
final unitPrice = signal<double>(context, 19.0);
final discount = signal<double>(context, 0.0);
final saves = signal<int>(context, 0);
final subtotal = computed<double>(context, (_) => qty() * unitPrice());
final total = computed<double>(
context,
(_) => subtotal() * (1 - discount()),
);
effect(context, () {
total();
final current = untrack(() => saves());
saves.set(current + 1);
});
String money(double value) => value.toStringAsFixed(2);
String percent(double value) => '${(value * 100).round()}%';
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('qty: ${qty()}'),
Text('unit: \$${money(unitPrice())}'),
Text('subtotal: \$${money(subtotal())}'),
Text('discount: ${percent(discount())}'),
Text('total: \$${money(total())}'),
Text('autosave runs: ${saves()}'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: [
ElevatedButton(
onPressed: () => qty.set(qty() + 1),
child: const Text('Add 1 item'),
),
OutlinedButton(
onPressed: qty() <= 1 ? null : () => qty.set(qty() - 1),
child: const Text('Remove 1 item'),
),
TextButton(
onPressed: () => discount.set(discount() == 0 ? 0.1 : 0.0),
child: const Text('Toggle 10% promo'),
),
TextButton(
onPressed: () => unitPrice.set(24.0),
child: const Text('Set price = 24'),
),
],
),
],
);
}
}