Skip to main content

graft 0.1.2-alpha.5 copy "graft: ^0.1.2-alpha.5" to clipboard
graft: ^0.1.2-alpha.5 copied to clipboard

Self-optimizing state management engine for Flutter: BLoC-style domain models, Signals-level micro-rebuild precision, and register-level CPU bitmask diffing.

example/lib/main.dart

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

// =============================================================================
// 1. STATE DEFINITION (Clean, zero-boilerplate domain model with GraftState)
// =============================================================================

class UserState extends GraftState {
  String name;
  String email;
  bool isVerified;
  int notificationCount;

  UserState({
    this.name = 'Alice Johnson',
    this.email = 'alice@example.com',
    this.isVerified = true,
    this.notificationCount = 3,
  });

  @override
  GraftProps get props => propsOf(name, email, isVerified, notificationCount);
}

// =============================================================================
// 2. GRAFT (Business Logic & State Transitions with direct cascade updates)
// =============================================================================

class UserGraft extends Graft<UserState> {
  UserGraft() : super(UserState());

  void updateName(String newName) {
    state
      ..name = newName
      ..update();
  }

  void updateEmail(String newEmail) {
    state
      ..email = newEmail
      ..update();
  }

  void toggleVerified() {
    state
      ..isVerified = !state.isVerified
      ..update();
  }

  void incrementNotifications() {
    state
      ..notificationCount += 1
      ..update();
  }

  /// Batched Multi-Property Update:
  /// Updates name, email, verification, and notification count simultaneously
  /// in a single statement. Triggers exactly ONE diff pass with zero intermediate rebuild glitches.
  void batchUpdateProfile({
    required String name,
    required String email,
    required bool isVerified,
    required int notifications,
  }) {
    state
      ..name = name
      ..email = email
      ..isVerified = isVerified
      ..notificationCount = notifications
      ..update(); // 💥 All 4 fields updated in 1 single atomic pass!
  }
}

// -----------------------------------------------------------------------------
// Pattern A Models: Standard List (Single Graft State)
// -----------------------------------------------------------------------------

class TaskItem {
  final String id;
  final String title;
  final bool isDone;
  TaskItem({required this.id, required this.title, required this.isDone});
}

class TaskListState extends GraftState {
  List<TaskItem> tasks;
  TaskListState({required this.tasks});

  @override
  GraftProps get props => propsOf(tasks);
}

class TaskListGraft extends Graft<TaskListState> {
  TaskListGraft()
      : super(
          TaskListState(
            tasks: [
              TaskItem(id: '1', title: 'Install Graft package', isDone: true),
              TaskItem(
                id: '2',
                title: 'Learn graft.slot and graft.slots',
                isDone: true,
              ),
              TaskItem(
                id: '3',
                title: 'Explore ListView with and without ValueGraft',
                isDone: false,
              ),
              TaskItem(
                id: '4',
                title: 'Build high-performance Flutter app',
                isDone: false,
              ),
            ],
          ),
        );

  void toggleTask(String id) {
    state
      ..tasks = state.tasks
          .map(
            (t) => t.id == id
                ? TaskItem(id: t.id, title: t.title, isDone: !t.isDone)
                : t,
          )
          .toList()
      ..update();
  }

  void addTask(String title) {
    state
      ..tasks = [
        ...state.tasks,
        TaskItem(
          id: DateTime.now().millisecondsSinceEpoch.toString(),
          title: title,
          isDone: false,
        ),
      ]
      ..update();
  }
}

// -----------------------------------------------------------------------------
// Pattern B Models: Micro-State (Per-Item ValueGraft)
// -----------------------------------------------------------------------------

class ProductItem {
  final String title;
  final ValueGraft<bool> isLiked;
  final ValueGraft<int> quantity;

  ProductItem({required this.title, bool liked = false, int count = 1})
      : isLiked = ValueGraft<bool>(liked),
        quantity = ValueGraft<int>(count);
}

class ProductCatalogState extends GraftState {
  List<ProductItem> products;
  bool isLoading;

  ProductCatalogState({this.products = const [], this.isLoading = true});

  @override
  GraftProps get props => propsOf(products, isLoading);
}

class ProductCatalogGraft extends Graft<ProductCatalogState> {
  ProductCatalogGraft() : super(ProductCatalogState());

  /// Asynchronous, non-blocking initialization:
  /// Simulates fetching items from an API or database in the background.
  Future<void> init() async {
    // Idempotent guard: do nothing if already loaded or in progress
    if (state.products.isNotEmpty || !state.isLoading) return;

    // Simulate non-blocking async network / I/O latency:
    await Future<void>.delayed(const Duration(milliseconds: 600));

    // Guard against disposal while async delay was pending:
    if (isDisposed) return;

    state
      ..products = List.generate(
        25,
        (i) => ProductItem(
          title: 'Item #${i + 1} (Flutter Widget)',
          liked: i % 2 == 0,
        ),
      )
      ..isLoading = false
      ..update();
  }

  @override
  void dispose() {
    for (final p in state.products) {
      p.isLiked.dispose();
      p.quantity.dispose();
    }
    super.dispose();
  }
}

// -----------------------------------------------------------------------------
// Pattern C Models: Independent Counter Graft for Multi-Graft Composition
// -----------------------------------------------------------------------------

class LiveCounterState extends GraftState {
  int count;
  LiveCounterState({this.count = 0});

  @override
  GraftProps get props => propsOf(count);

  @override
  void onReset() {
    count = 0;
  }
}

class LiveCounterGraft extends Graft<LiveCounterState> {
  LiveCounterGraft() : super(LiveCounterState());

  void increment() {
    state
      ..count += 1
      ..update();
  }

  void decrement() {
    state
      ..count -= 1
      ..update();
  }
}

// =============================================================================
// 3. MAIN ENTRY POINT & APP SETUP
// =============================================================================

void main() {
  // 1. Enable colorized console dev logging:
  Graft.observer = GraftDevObserver();

  // 2. Register Graft factories in DI:
  GraftRegistry.register(UserGraft.new);
  GraftRegistry.register(TaskListGraft.new);
  GraftRegistry.register(ProductCatalogGraft.new);
  GraftRegistry.register(LiveCounterGraft.new);

  runApp(const GraftExampleApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Graft State Management Showcase',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.deepPurple,
          brightness: Brightness.light,
        ),
        useMaterial3: true,
      ),
      // 3. Attach navigator observer for route stack inheritance & auto-disposal:
      navigatorObservers: [GraftRouteObserver()],
      home: const HomeScreen(),
    );
  }
}

// =============================================================================
// 4. SCREEN 1: HOME SCREEN (First Initializer & Owner)
// =============================================================================

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

  @override
  Widget build(BuildContext context) {
    // Auto-instantiates and borrows across the navigation stack:
    final graft = context.use(UserGraft.new);
    final counter = context.use(LiveCounterGraft.new);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Graft Showcase 🌱'),
        actions: [
          // Pre-flight derived computation: graft.compute
          graft.compute(
            compute: (s) => s.notificationCount,
            builder: (count) => IconButton(
              icon: Badge(
                label: Text('$count'),
                child: const Icon(Icons.notifications),
              ),
              onPressed: () => graft.incrementNotifications(),
            ),
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // =================================================================
            // Multi-Child Slot Diffing: graft.slots
            // =================================================================
            const _SectionHeader(
              title: '1. Multi-Child Slot Diffing (graft.slots)',
              subtitle:
                  'Only changed slots rebuild! Const widgets have 0 rebuilds.',
            ),
            const SizedBox(height: 8),

            graft.slots(
              layout: const Column(children: slots),
              slots: (s) => [
                // Const widget directly without wrapping: 0 rebuilds, 0 closure evaluations
                const Card(
                  color: Colors.deepPurple,
                  child: Padding(
                    padding: EdgeInsets.all(12),
                    child: Text(
                      '⚡ Const Header Banner (0 rebuilds)',
                      style: TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                ),

                Container(
                  padding: const EdgeInsets.all(12),
                  child: GraftBoundary(builder: (_) => Text(s.name)),
                ),

                // Slot 1: Name
                ListTile(
                  leading: const Icon(Icons.person, color: Colors.deepPurple),
                  title: Text('Name: ${s.name}'),
                  subtitle: const Text('Rebuilds ONLY when name changes'),
                ),

                // Slot 2: Email
                ListTile(
                  leading: const Icon(Icons.email, color: Colors.deepPurple),
                  title: Text('Email: ${s.email}'),
                  subtitle: const Text('0 rebuilds if email is unchanged'),
                ),

                // Slot 3: Conditional Badge
                if (s.isVerified)
                  const Chip(
                    avatar: Icon(Icons.verified, color: Colors.green),
                    label: Text('Verified User Account'),
                  )
                else
                  const SizedBox.shrink(),
              ],
            ),

            const SizedBox(height: 16),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                ElevatedButton.icon(
                  icon: const Icon(Icons.edit),
                  label: const Text('Change Name'),
                  onPressed: () => graft.updateName(
                    graft.state.name == 'Alice Johnson'
                        ? 'Bob Smith'
                        : 'Alice Johnson',
                  ),
                ),
                ElevatedButton.icon(
                  icon: const Icon(Icons.alternate_email),
                  label: const Text('Change Email'),
                  onPressed: () => graft.updateEmail(
                    graft.state.email == 'alice@example.com'
                        ? 'bob@example.com'
                        : 'alice@example.com',
                  ),
                ),
                OutlinedButton.icon(
                  icon: const Icon(Icons.check_circle_outline),
                  label: const Text('Toggle Verified'),
                  onPressed: () => graft.toggleVerified(),
                ),
                FilledButton.icon(
                  icon: const Icon(Icons.flash_on),
                  label: const Text('⚡ Batch Update All Fields'),
                  onPressed: () {
                    final isOriginal = graft.state.name == 'Alice Johnson';
                    graft.batchUpdateProfile(
                      name: isOriginal ? 'Dr. John Doe' : 'Alice Johnson',
                      email: isOriginal
                          ? 'john.doe@company.org'
                          : 'alice@example.com',
                      isVerified: !graft.state.isVerified,
                      notifications: graft.state.notificationCount + 5,
                    );
                  },
                ),
              ],
            ),

            const SizedBox(height: 24),
            const Divider(),

            // =================================================================
            // Standard Flutter Widgets Composed with graft.slot
            // =================================================================
            const _SectionHeader(
              title: '2. Standard Widgets Composed with graft.slot',
              subtitle:
                  'Place graft.slot inside any Flutter widget (ListTile, Card, etc.).',
            ),
            const SizedBox(height: 8),

            ListTile(
              leading: graft.slot(
                builder: (s) => CircleAvatar(
                  backgroundColor: s.isVerified ? Colors.green : Colors.grey,
                  child: Text(s.name.isNotEmpty ? s.name[0] : '?'),
                ),
              ),
              title: graft.slot(
                builder: (s) => Text(
                  s.name,
                  style: const TextStyle(fontWeight: FontWeight.bold),
                ),
              ),
              subtitle: graft.slot(builder: (s) => Text(s.email)),
              trailing: const Icon(Icons.arrow_forward_ios, size: 16),
            ),

            const SizedBox(height: 8),

            Card(
              color: Colors.deepPurple.shade50,
              child: graft.slot(
                builder: (s) => Padding(
                  padding: const EdgeInsets.all(12),
                  child: Text(
                    'Card slot diffed: ${s.name} (${s.isVerified ? "Verified" : "Unverified"})',
                  ),
                ),
              ),
            ),

            const SizedBox(height: 24),
            const Divider(),

            // =================================================================
            // 3. GraftBoundary: Ambient Auto-Discovery & Insulation
            // =================================================================
            const _SectionHeader(
              title:
                  '3. GraftBoundary: Multi-Graft Auto-Discovery & Insulation',
              subtitle:
                  'Ambiently auto-discovers multiple Grafts with zero manual lists and full rebuild insulation: GraftBoundary(builder: (context) => ...)',
            ),
            const SizedBox(height: 8),

            _HeavyShellContainer(
              child: GraftBoundary(
                builder: (context) => Row(
                  children: [
                    const Icon(Icons.hub_outlined, color: Colors.deepPurple),
                    const SizedBox(width: 8),
                    Expanded(
                      child: Text(
                        'User: ${graft.state.name}  •  Count: ${counter.state.count}',
                        style: const TextStyle(fontWeight: FontWeight.w600),
                      ),
                    ),
                    IconButton(
                      icon: const Icon(Icons.add_circle,
                          color: Colors.deepPurple),
                      tooltip: 'Increment Counter from Multi-Graft Boundary',
                      onPressed: () => counter.increment(),
                    ),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 24),
            const Divider(),

            // =================================================================
            // 4. Route-Stack Sharing & Lifecycle
            // =================================================================
            const _SectionHeader(
              title: '4. Route-Stack Lifecycle & Inheritance',
              subtitle:
                  'Pushed screens borrow the active Graft. Disposed when owner pops.',
            ),
            const SizedBox(height: 12),

            ElevatedButton.icon(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.deepPurple,
                foregroundColor: Colors.white,
                padding: const EdgeInsets.symmetric(vertical: 14),
              ),
              icon: const Icon(Icons.open_in_new),
              label: const Text('Push Edit Screen (Borrows Current Graft)'),
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(builder: (_) => const EditProfileScreen()),
                );
              },
            ),
            const SizedBox(height: 8),
            OutlinedButton.icon(
              icon: const Icon(Icons.add_circle_outline),
              label: const Text('Push Isolated Screen (context.create)'),
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (_) => const IsolatedProfileScreen(),
                  ),
                );
              },
            ),

            const SizedBox(height: 24),
            const Divider(),

            // =================================================================
            // 4. ListView.builder Examples (With & Without ValueGraft)
            // =================================================================
            const _SectionHeader(
              title: '4. ListView.builder Examples',
              subtitle:
                  'Compare standard single-Graft lists vs. per-item ValueGraft.',
            ),
            const SizedBox(height: 12),

            ElevatedButton.icon(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.teal.shade700,
                foregroundColor: Colors.white,
                padding: const EdgeInsets.symmetric(vertical: 14),
              ),
              icon: const Icon(Icons.format_list_bulleted),
              label: const Text(
                'Standard List (Single Graft / Without ValueGraft)',
              ),
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(builder: (_) => const StandardListScreen()),
                );
              },
            ),
            const SizedBox(height: 8),
            OutlinedButton.icon(
              style: OutlinedButton.styleFrom(
                padding: const EdgeInsets.symmetric(vertical: 14),
              ),
              icon: const Icon(Icons.bolt),
              label: const Text('Micro-State List (Per-Item ValueGraft)'),
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (_) => const ValueGraftListScreen(),
                  ),
                );
              },
            ),

            const SizedBox(height: 24),
            const Divider(),

            // =================================================================
            // 5. Multi-Graft Composition & Cross-Graft Nesting
            // =================================================================
            const _SectionHeader(
              title: '5. Multi-Graft Composition (Cross-Graft Nesting)',
              subtitle:
                  'Consume multiple Grafts in one screen with ZERO MultiBlocProvider pyramids.',
            ),
            const SizedBox(height: 12),

            ElevatedButton.icon(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.amber.shade800,
                foregroundColor: Colors.white,
                padding: const EdgeInsets.symmetric(vertical: 14),
              ),
              icon: const Icon(Icons.hub),
              label: const Text('Multi-Graft Showcase (Cross-Graft Nesting)'),
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (_) => const MultiGraftCompositionScreen(),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

// =============================================================================
// 5. SCREEN 2: EDIT PROFILE SCREEN (Borrows Instance from Route Stack)
// =============================================================================

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

  @override
  Widget build(BuildContext context) {
    // Automatically reuses the exact same UserGraft created by HomeScreen!
    final graft = context.use<UserGraft>();

    return Scaffold(
      appBar: AppBar(title: const Text('Borrower Screen 🔄')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: graft.slots(
          layout: const Column(children: slots),
          slots: (s) => [
            const Text(
              'This screen called context.use<UserGraft>() and borrowed the existing instance from HomeScreen.',
              style: TextStyle(fontSize: 15),
            ),
            const SizedBox(height: 16),
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Current Name: ${s.name}',
                      style: const TextStyle(fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 4),
                    Text('Current Email: ${s.email}'),
                    const SizedBox(height: 4),
                    Text('Status: ${s.isVerified ? "Verified" : "Unverified"}'),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                graft.updateName('Edited by Second Screen!');
                Navigator.of(context).pop();
              },
              child: const Text('Update Name & Pop Back to Home'),
            ),
          ],
        ),
      ),
    );
  }
}

// =============================================================================
// 6. SCREEN 3: ISOLATED SCREEN (Forces New Instance via context.create)
// =============================================================================

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

  @override
  Widget build(BuildContext context) {
    // Forces a brand new UserGraft owned specifically by this route:
    final graft = context.create<UserGraft>();

    return Scaffold(
      appBar: AppBar(title: const Text('Isolated Screen 🛡️')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: graft.slots(
          layout: const Column(children: slots),
          slots: (s) => [
            const Text(
              'This screen used context.create<UserGraft>() to create a completely independent instance.',
              style: TextStyle(fontSize: 15),
            ),
            const SizedBox(height: 16),
            Text('Independent Name: ${s.name}'),
            const SizedBox(height: 8),
            ElevatedButton(
              onPressed: () => graft.updateName('Isolated Change'),
              child: const Text('Change (Does NOT Affect Home)'),
            ),
          ],
        ),
      ),
    );
  }
}

// =============================================================================
// 7. SCREEN 4: STANDARD LISTVIEW (Without ValueGraft — Single Graft)
// =============================================================================

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

  @override
  Widget build(BuildContext context) {
    // Borrow or create route-scoped TaskListGraft:
    final graft = context.use<TaskListGraft>();

    return Scaffold(
      appBar: AppBar(
        title: const Text('Standard List (Single Graft)'),
        actions: [
          IconButton(
            icon: const Icon(Icons.add),
            tooltip: 'Add Task',
            onPressed: () {
              graft.addTask('New Task #${graft.state.tasks.length + 1}');
            },
          ),
        ],
      ),
      body: Column(
        children: [
          // Pattern A Explanation Card
          Card(
            margin: const EdgeInsets.all(12),
            color: Colors.teal.shade50,
            elevation: 0,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(12),
              side: BorderSide(color: Colors.teal.shade200),
            ),
            child: const Padding(
              padding: EdgeInsets.all(12),
              child: Text(
                '💡 Pattern A: 100% Lazy Virtualization with graft.builder & graft.item\n'
                '• 100% Lazy & Virtualized: Only visible items are in RAM (scales to 10,000+ items)!\n'
                '• Per-Item Diffing: Toggling a task rebuilds ONLY that 1 item — 0 rebuilds for other items!\n'
                '• Universal: Use graft.builder for lists, or plug graft.item into GridView.builder / PageView.builder!',
                style: TextStyle(fontSize: 13, height: 1.4),
              ),
            ),
          ),
          Expanded(
            child: graft.builder<TaskItem>(
              items: (s) => s.tasks,
              itemBuilder: (task, index) {
                return Card(
                  child: ListTile(
                    leading: Checkbox(
                      value: task.isDone,
                      onChanged: (_) => graft.toggleTask(task.id),
                    ),
                    title: Text(
                      task.title,
                      style: TextStyle(
                        decoration:
                            task.isDone ? TextDecoration.lineThrough : null,
                        color: task.isDone ? Colors.grey : null,
                      ),
                    ),
                    trailing: Chip(
                      label: Text(task.isDone ? 'Done' : 'Pending'),
                      backgroundColor: task.isDone
                          ? Colors.green.shade100
                          : Colors.amber.shade100,
                    ),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

// =============================================================================
// 8. SCREEN 5: MICRO-STATE LISTVIEW (With ValueGraft per item)
// =============================================================================

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

  @override
  Widget build(BuildContext context) {
    // 💥 100% Stateless: Borrow or create route-scoped catalog Graft & trigger async init:
    final catalog = context.use<ProductCatalogGraft>()..init();

    return Scaffold(
      appBar: AppBar(title: const Text('Micro-State List (ValueGraft)')),
      body: Column(
        children: [
          // Pattern B Explanation Card
          Card(
            margin: const EdgeInsets.all(12),
            color: Colors.deepPurple.shade50,
            elevation: 0,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(12),
              side: BorderSide(color: Colors.deepPurple.shade200),
            ),
            child: const Padding(
              padding: EdgeInsets.all(12),
              child: Text(
                '💡 Pattern B: Per-Item Micro-State with ValueGraft\n'
                '• 100% StatelessWidget! No StatefulWidget or setState required.\n'
                '• Non-blocking async init() simulates remote fetching (spinner -> list).\n'
                '• Once loaded, tapping "+" or "♥" rebuilds ONLY that specific cell slot — 0 parent & sibling rebuilds!',
                style: TextStyle(fontSize: 13, height: 1.4),
              ),
            ),
          ),
          Expanded(
            child: catalog.slot(builder: (s) {
              if (s.isLoading) {
                return const Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      CircularProgressIndicator(),
                      SizedBox(height: 12),
                      Text('Loading products (non-blocking async)...'),
                    ],
                  ),
                );
              }

              return ListView.builder(
                padding: const EdgeInsets.symmetric(horizontal: 8),
                itemCount: s.products.length,
                itemBuilder: (context, index) {
                  final product = s.products[index];

                  return Card(
                    child: ListTile(
                      leading: CircleAvatar(
                        backgroundColor: Colors.deepPurple.shade100,
                        child: Text('${index + 1}'),
                      ),
                      title: Text(product.title),
                      subtitle: product.quantity.slot(
                        // 💥 Only this quantity label rebuilds when incremented/decremented!
                        builder: (qty) => Text('In Cart: $qty units'),
                      ),
                      trailing: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          IconButton(
                            icon: const Icon(
                              Icons.remove_circle_outline,
                              size: 20,
                            ),
                            onPressed: () {
                              if (product.quantity.value > 1) {
                                product.quantity.value--;
                              }
                            },
                          ),
                          IconButton(
                            icon: const Icon(
                              Icons.add_circle_outline,
                              size: 20,
                            ),
                            onPressed: () => product.quantity.value++,
                          ),
                          const SizedBox(width: 8),
                          product.isLiked.slot(
                            // 💥 Only this heart icon rebuilds when toggled!
                            builder: (isLiked) => IconButton(
                              icon: Icon(
                                isLiked
                                    ? Icons.favorite
                                    : Icons.favorite_border,
                                color: isLiked ? Colors.red : null,
                              ),
                              onPressed: () => product.isLiked.value =
                                  !product.isLiked.value,
                            ),
                          ),
                        ],
                      ),
                    ),
                  );
                },
              );
            }),
          ),
        ],
      ),
    );
  }
}

// =============================================================================
// 9. SCREEN 6: MULTI-GRAFT COMPOSITION & CROSS-GRAFT NESTING
// =============================================================================

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

  @override
  Widget build(BuildContext context) {
    // 💥 ZERO TREE NESTING:
    // Consume multiple independent Grafts directly at the top of any StatelessWidget!
    // No MultiBlocProvider, no MultiProvider, no ConsumerWidget!
    final userGraft = context.use<UserGraft>();
    final counterGraft = context.use<LiveCounterGraft>();

    return Scaffold(
      appBar: AppBar(title: const Text('Multi-Graft Showcase 🧬')),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            const _SectionHeader(
              title: 'Multi-Graft Composition & Nested Isolation',
              subtitle:
                  'Consume multiple Grafts with zero pyramids and complete rebuild isolation.',
            ),
            const SizedBox(height: 16),

            // Card explaining the behavior:
            Card(
              color: Colors.blue.shade50,
              elevation: 0,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(12),
                side: BorderSide(color: Colors.blue.shade200),
              ),
              child: const Padding(
                padding: EdgeInsets.all(12),
                child: Text(
                  '💡 How it works:\n'
                  '• Tapping "Change Name" updates ONLY the parent User Card slot. The amber Counter container below stays at 0 rebuilds!\n'
                  '• Tapping "+1" updates ONLY the counter text slot. The parent User Card stays at 0 rebuilds!\n'
                  '• Both Grafts are independent controllers with 100% mutual rebuild isolation.',
                  style: TextStyle(fontSize: 13, height: 1.4),
                ),
              ),
            ),
            const SizedBox(height: 16),
            userGraft.slots(
              layout: const Column(children: slots),
              slots: (userState) => [
                // Slot 0: User Profile Card (Parent Graft)
                Card(
                  elevation: 2,
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const Row(
                          children: [
                            Icon(
                              Icons.account_circle,
                              color: Colors.deepPurple,
                              size: 28,
                            ),
                            SizedBox(width: 8),
                            Text(
                              'Parent Graft: UserGraft',
                              style: TextStyle(
                                fontWeight: FontWeight.bold,
                                fontSize: 16,
                              ),
                            ),
                          ],
                        ),
                        const Divider(height: 20),
                        Text(
                          'Name: ${userState.name}',
                          style: const TextStyle(
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        const SizedBox(height: 4),
                        Text(
                          'Email: ${userState.email}',
                          style: TextStyle(color: Colors.grey.shade700),
                        ),
                        const SizedBox(height: 12),
                        ElevatedButton.icon(
                          icon: const Icon(Icons.edit),
                          label: const Text('Change Name'),
                          onPressed: () => userGraft.updateName(
                            userGraft.state.name == 'Alice Johnson'
                                ? 'Dr. John Doe'
                                : 'Alice Johnson',
                          ),
                        ),
                      ],
                    ),
                  ),
                ),

                const SizedBox(height: 16),

                // Slot 1: NESTED INDEPENDENT GRAFT (counterGraft.slots)
                // Embedded inside a styled Container within UserGraft's slots!
                // Thanks to GraftEquivalent, UserGraft updates do NOT rebuild this Container!
                Container(
                  padding: const EdgeInsets.all(16),
                  decoration: BoxDecoration(
                    color: Colors.amber.shade100,
                    borderRadius: BorderRadius.circular(16),
                    border: Border.all(color: Colors.amber.shade700, width: 2),
                  ),
                  child: counterGraft.slots(
                    layout: const Column(children: slots),
                    slots: (counterState) => [
                      Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Icon(Icons.bolt, color: Colors.amber.shade900),
                          const SizedBox(width: 6),
                          Text(
                            'Nested Child Graft: LiveCounterGraft',
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              color: Colors.brown.shade900,
                            ),
                          ),
                        ],
                      ),
                      const SizedBox(height: 8),
                      Text(
                        'Count: ${counterState.count}',
                        style: TextStyle(
                          fontSize: 28,
                          fontWeight: FontWeight.bold,
                          color: Colors.brown.shade900,
                        ),
                      ),
                      const SizedBox(height: 12),
                      Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          IconButton.filled(
                            style: IconButton.styleFrom(
                              backgroundColor: Colors.amber.shade800,
                            ),
                            icon: const Icon(Icons.remove),
                            tooltip: 'Decrement',
                            onPressed: counterGraft.decrement,
                          ),
                          const SizedBox(width: 16),
                          IconButton.filled(
                            style: IconButton.styleFrom(
                              backgroundColor: Colors.amber.shade800,
                            ),
                            icon: const Icon(Icons.refresh),
                            tooltip: 'Reset to 0',
                            onPressed: counterGraft.reset,
                          ),
                          const SizedBox(width: 16),
                          IconButton.filled(
                            style: IconButton.styleFrom(
                              backgroundColor: Colors.amber.shade800,
                            ),
                            icon: const Icon(Icons.add),
                            tooltip: 'Increment',
                            onPressed: counterGraft.increment,
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// =============================================================================
// HELPER WIDGETS
// =============================================================================

class _SectionHeader extends StatelessWidget {
  final String title;
  final String subtitle;

  const _SectionHeader({required this.title, required this.subtitle});

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          title,
          style: const TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 2),
        Text(
          subtitle,
          style: TextStyle(fontSize: 13, color: Colors.grey.shade700),
        ),
      ],
    );
  }
}

class _HeavyShellContainer extends StatefulWidget {
  final Widget child;
  const _HeavyShellContainer({required this.child});

  @override
  State<_HeavyShellContainer> createState() => _HeavyShellContainerState();
}

class _HeavyShellContainerState extends State<_HeavyShellContainer> {
  int _buildCount = 0;

  @override
  Widget build(BuildContext context) {
    _buildCount++;
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16),
        border: Border.all(color: Colors.deepPurple.shade200, width: 2),
        boxShadow: [
          BoxShadow(
            color: Colors.deepPurple.withValues(alpha: 0.08),
            blurRadius: 12,
            offset: const Offset(0, 4),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              const Text(
                '🛡️ Outer Parent Container',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              Container(
                padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                decoration: BoxDecoration(
                  color: _buildCount == 1
                      ? Colors.green.shade100
                      : Colors.red.shade100,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Text(
                  'Parent Builds: $_buildCount (Locked!)',
                  style: TextStyle(
                    fontSize: 12,
                    fontWeight: FontWeight.bold,
                    color: _buildCount == 1
                        ? Colors.green.shade800
                        : Colors.red.shade800,
                  ),
                ),
              ),
            ],
          ),
          const SizedBox(height: 8),
          const Text(
            'This parent container NEVER rebuilds when child state updates:',
            style: TextStyle(fontSize: 13, color: Colors.black54),
          ),
          const Divider(height: 20),
          widget.child,
        ],
      ),
    );
  }
}
2
likes
0
points
592
downloads

Publisher

unverified uploader

Weekly Downloads

Self-optimizing state management engine for Flutter: BLoC-style domain models, Signals-level micro-rebuild precision, and register-level CPU bitmask diffing.

Repository (GitHub)
View/report issues

Topics

#state-management #reactivity #performance #dependency-injection #widget

License

unknown (license)

Dependencies

flutter, flutter_test

More

Packages that depend on graft