bloomBUILD•SHIP•BLOOM
.md
Fine-Grained Reactivity

Signals Reactivity

Bloom's reactivity is powered by fine-grained signals. Only the exact leaf widgets reading a signal re-render when state mutates—giving you 60fps Impeller performance without setState() overhead.

Signals, Computed & Effects

The core reactivity triad provides predictable, glitch-free dependency tracking:

Signals API
dart
// 1. Primitive Signals
final count = createSignal<int>(0);
print(count.value); // 0
count.value = 5;    // Notifies dependents automatically

// 2. Computed Derived State (Lazy & Memoized)
final doubleCount = createComputed(() => count.value * 2);
print(doubleCount.value); // 10

// 3. Side Effects
final disposeEffect = createEffect(() {
  print('Count changed to: ${count.value}');
});

BloomController Encapsulation

Organize business logic into controllers. BloomController manages signal lifecycles and automatically cleans up active effect subscriptions when disposed:

lib/features/todos/todo_controller.dart
dart
// lib/features/todos/todo_controller.dart
import 'package:bloom_framework/bloom.dart';

class TodoItem {
  final String id;
  final String title;
  final bool completed;
  TodoItem({required this.id, required this.title, this.completed = false});
}

class TodoController extends BloomController {
  late final todos = createSignal<List<TodoItem>>([]);
  late final filter = createSignal<String>('all');

  // Computed filtered list
  late final visibleTodos = createComputed<List<TodoItem>>(() {
    final currentFilter = filter.value;
    final all = todos.value;
    if (currentFilter == 'active') return all.where((t) => !t.completed).toList();
    if (currentFilter == 'completed') return all.where((t) => t.completed).toList();
    return all;
  });

  void addTodo(String title) {
    todos.value = [
      ...todos.value,
      TodoItem(id: DateTime.now().millisecondsSinceEpoch.toString(), title: title),
    ];
  }

  void toggleTodo(String id) {
    todos.value = todos.value.map((t) {
      if (t.id == id) {
        return TodoItem(id: t.id, title: t.title, completed: !t.completed);
      }
      return t;
    }).toList();
  }
}

Granular UI Subscriptions with Watch

Wrap only the widgets that depend on dynamic state with Watch. Parent scaffold and layout widgets never rebuild unnecessarily:

lib/routes/todos.dart
dart
// lib/routes/todos.dart
import 'package:flutter/material.dart';
import 'package:bloom_framework/bloom.dart';
import '../features/todos/todo_controller.dart';

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

  @override
  Widget build(BuildContext context) {
    final controller = inject<TodoController>();

    return Scaffold(
      appBar: AppBar(title: const Text('Todo List')),
      body: Column(
        children: [
          // Subscribes and rebuilds ONLY this ListView when visibleTodos updates
          Expanded(
            child: Watch((context) {
              final list = controller.visibleTodos.value;
              if (list.isEmpty) {
                return const Center(child: Text('No tasks found.'));
              }
              return ListView.builder(
                itemCount: list.length,
                itemBuilder: (context, i) {
                  final item = list[i];
                  return CheckboxListTile(
                    title: Text(
                      item.title,
                      style: TextStyle(
                        decoration: item.completed ? TextDecoration.lineThrough : null,
                      ),
                    ),
                    value: item.completed,
                    onChanged: (_) => controller.toggleTodo(item.id),
                  );
                },
              );
            }),
          ),
        ],
      ),
    );
  }
}