pipe_x 1.7.0 copy "pipe_x: ^1.7.0" to clipboard
pipe_x: ^1.7.0 copied to clipboard

A lightweight, reactive state management library for Flutter with fine-grained reactivity and minimal boilerplate.

example/lib/main.dart

import 'package:dartz/dartz.dart' hide State;
import 'package:flutter/material.dart';
import 'package:pipe_x/pipe_x.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PipeX State Management Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
        useMaterial3: true,
      ),
      initialRoute: '/',
      onGenerateRoute: _onGenerateRoute,
    );
  }

  static Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
    switch (settings.name) {
      case '/':
        return MaterialPageRoute(builder: (_) => const ExamplesListScreen());
      case '/counter':
        return MaterialPageRoute(
          builder: (_) => HubProvider(
            create: () => CounterHub(),
            child: const CounterExample(),
          ),
        );
      case '/multiple-pipes':
        return MaterialPageRoute(
          builder: (_) => HubProvider(
            create: () => UserHub(),
            child: const MultiplePipesExample(),
          ),
        );
      case '/standalone-pipe':
        return MaterialPageRoute(builder: (_) => const StandalonePipeExample());
      case '/single-sink':
        return MaterialPageRoute(
          builder: (_) => HubProvider(
            create: () => TimerHub()..startTimer(),
            child: const SingleSinkExample(),
          ),
        );
      case '/multiple-sinks':
        return MaterialPageRoute(
          builder: (_) => HubProvider(
            create: () => MultiCounterHub(),
            child: const MultipleSinksExample(),
          ),
        );
      case '/well':
        return MaterialPageRoute(
          builder: (_) => HubProvider(
            create: () => CalculatorHub(),
            child: const WellExample(),
          ),
        );
      case '/hub-provider':
        return MaterialPageRoute(
          builder: (_) => HubProvider(
            create: () => ThemeHub(),
            child: const HubProviderExample(),
          ),
        );
      case '/multi-hub-provider':
        return MaterialPageRoute(
          builder: (_) => MultiHubProvider(
            hubs: [
              () => AuthHub(),
              () => SettingsHub(),
            ],
            child: const MultiHubProviderExample(),
          ),
        );
      case '/hub-provider-value':
        // Pre-create the hub instance
        final preCreatedHub = ThemeHub();
        return MaterialPageRoute(
          builder: (_) => HubProvider<ThemeHub>.value(
            value: preCreatedHub,
            child: const HubProviderValueExample(),
          ),
        );
      case '/multi-hub-provider-value':
        // Pre-create hub instances
        final authHub = AuthHub();
        final settingsHub = SettingsHub();
        return MaterialPageRoute(
          builder: (_) => MultiHubProvider(
            hubs: [
              authHub, // Existing instance
              settingsHub, // Existing instance
            ],
            child: const MultiHubProviderValueExample(),
          ),
        );
      case '/scoped-vs-global':
        return MaterialPageRoute(
          builder: (_) => HubProvider(
            create: () => CounterGlobalHub(),
            child: const ScopedVsGlobalExample(),
          ),
        );
      case '/computed-values':
        return MaterialPageRoute(
          builder: (_) => HubProvider(
            create: () => ShoppingHub(),
            child: const ComputedValuesExample(),
          ),
        );
      case '/async-basic':
        return MaterialPageRoute(
          builder: (_) => HubProvider(
            create: () => SimpleAsyncHub(),
            child: const SimpleAsyncExample(),
          ),
        );
      case '/async-advanced':
        // Ensure services are registered before creating DataHub
        if (!ServiceLocator.instance.isRegistered<IUserRepository>()) {
          setupServices();
        }
        return MaterialPageRoute(
          builder: (_) => HubProvider(
            create: () => DataHub(),
            child: const AsyncExample(),
          ),
        );
      case '/form':
        return MaterialPageRoute(
          builder: (_) => HubProvider(
            create: () => FormHub(),
            child: const FormExample(),
          ),
        );
      case '/class-type':
        return MaterialPageRoute(
          builder: (_) => HubProvider(
            create: () => UserProfileHub(),
            child: const ClassTypeExample(),
          ),
        );
      case '/hub-listener':
        return MaterialPageRoute(
          builder: (_) => HubProvider(
            create: () => TargetCounterHub(),
            child: const HubListenerExample(),
          ),
        );
      default:
        return null;
    }
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('💧 PipeX Examples'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: ListView(
        children: [
          _buildSection(context, '📦 Basic Examples', const [
            _Example('Counter with Hub', '/counter'),
            _Example('Multiple Pipes', '/multiple-pipes'),
            _Example('Standalone Pipe (Auto-dispose)', '/standalone-pipe'),
            _Example('Class Type in Pipe', '/class-type'),
          ]),
          _buildSection(context, '🔄 Reactive Widgets', const [
            _Example('Single Sink', '/single-sink'),
            _Example('Multiple Sinks', '/multiple-sinks'),
            _Example('Well (Multiple Pipes)', '/well'),
          ]),
          _buildSection(context, '🏗️ Dependency Injection', const [
            _Example('HubProvider Basics', '/hub-provider'),
            _Example('MultiHubProvider', '/multi-hub-provider'),
            _Example('HubProvider.value (External Lifecycle)',
                '/hub-provider-value'),
            _Example(
                'MultiHubProvider with Values', '/multi-hub-provider-value'),
            _Example('Scoped vs Global', '/scoped-vs-global'),
          ]),
          _buildSection(context, '⚡ Advanced Patterns', const [
            _Example('Computed Values (Computed Pipe)', '/computed-values'),
            _Example('Async Operations (Basic)', '/async-basic'),
            _Example('Async Operations (Either + DI)', '/async-advanced'),
            _Example('Form Management', '/form'),
            _Example('HubListener (Conditional Side Effects)', '/hub-listener'),
          ]),
        ],
      ),
    );
  }

  Widget _buildSection(
      BuildContext context, String title, List<_Example> examples) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.fromLTRB(16, 24, 16, 8),
          child: Text(
            title,
            style: Theme.of(context).textTheme.titleLarge?.copyWith(
                  fontWeight: FontWeight.bold,
                ),
          ),
        ),
        ...examples.map((example) => ListTile(
              leading: const Icon(Icons.arrow_forward_ios, size: 16),
              title: Text(example.title),
              onTap: () => Navigator.pushNamed(context, example.route),
            )),
        const Divider(),
      ],
    );
  }
}

class _Example {
  final String title;
  final String route;
  const _Example(this.title, this.route);
}

// ============================================================================
// EXAMPLE 1: Counter with Hub
// ============================================================================

class CounterHub extends Hub {
  late final count = pipe(0);

  void increment() => count.value++;
  void decrement() => count.value--;
  void reset() => count.value = 0;
}

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

  @override
  Widget build(BuildContext context) {
    final hub = context.read<CounterHub>();

    return Scaffold(
      appBar: AppBar(title: const Text('Counter Example')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Basic counter with Hub & Sink:'),
            const SizedBox(height: 16),
            Sink(
              pipe: hub.count,
              builder: (context, value) {
                return Text(
                  '$value',
                  style: Theme.of(context).textTheme.displayLarge,
                );
              },
            ),
            const SizedBox(height: 24),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                FloatingActionButton(
                  heroTag: 'dec',
                  onPressed: () => hub.decrement(),
                  child: const Icon(Icons.remove),
                ),
                const SizedBox(width: 16),
                FloatingActionButton(
                  heroTag: 'inc',
                  onPressed: () => hub.increment(),
                  child: const Icon(Icons.add),
                ),
              ],
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () => hub.reset(),
              child: const Text('Reset'),
            ),
          ],
        ),
      ),
    );
  }
}

// ============================================================================
// EXAMPLE 2: Multiple Pipes
// ============================================================================

class UserHub extends Hub {
  late final name = pipe('John Doe');
  late final age = pipe(25);
  late final email = pipe('[email protected]');

  // Computed value using computed pipe
  late final summary = computedPipe(
    dependencies: [name, age],
    compute: () => '${name.value}, ${age.value} years old',
  );
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Multiple Pipes')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text('Multiple independent pipes in one Hub:'),
            const SizedBox(height: 24),
            Sink(
              pipe: context.read<UserHub>().name,
              builder: (context, value) =>
                  Text('Name: $value', style: const TextStyle(fontSize: 18)),
            ),
            const SizedBox(height: 8),
            Sink(
              pipe: context.read<UserHub>().age,
              builder: (context, value) =>
                  Text('Age: $value', style: const TextStyle(fontSize: 18)),
            ),
            const SizedBox(height: 8),
            Sink(
              pipe: context.read<UserHub>().email,
              builder: (context, value) =>
                  Text('Email: $value', style: const TextStyle(fontSize: 18)),
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: () {
                final hub = context.read<UserHub>();
                hub.name.value = 'Jane Smith';
                hub.age.value = 30;
                hub.email.value = '[email protected]';
              },
              child: const Text('Update User'),
            ),
          ],
        ),
      ),
    );
  }
}

// ============================================================================
// EXAMPLE 3: Standalone Pipe (Auto-dispose)
// ============================================================================

class StandalonePipeExample extends StatefulWidget {
  const StandalonePipeExample({super.key});

  @override
  State<StandalonePipeExample> createState() => _StandalonePipeExampleState();
}

class _StandalonePipeExampleState extends State<StandalonePipeExample> {
  late final Pipe<int> counter;
  late final Pipe<String> message;

  @override
  void initState() {
    super.initState();
    // These pipes are created outside a Hub, so they'll auto-dispose
    counter = Pipe(0);
    message = Pipe('Hello!');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Standalone Pipe')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Pipes without Hub (auto-dispose on unmount):'),
            const SizedBox(height: 24),
            Sink(
              pipe: counter,
              builder: (context, value) => Text(
                'Counter: $value',
                style: const TextStyle(fontSize: 24),
              ),
            ),
            const SizedBox(height: 16),
            Sink(
              pipe: message,
              builder: (context, value) => Text(
                value,
                style: const TextStyle(fontSize: 18, color: Colors.blue),
              ),
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: () {
                counter.value++;
                message.value = 'Count: ${counter.value}';
              },
              child: const Text('Increment'),
            ),
            const SizedBox(height: 16),
            Text(
              'These pipes will auto-dispose when you go back',
              style: TextStyle(fontSize: 12, color: Colors.grey[600]),
            ),
          ],
        ),
      ),
    );
  }
}

// ============================================================================
// EXAMPLE 4: Single Sink
// ============================================================================

class TimerHub extends Hub {
  late final seconds = pipe(0);

  void startTimer() {
    Future.doWhile(() async {
      await Future.delayed(const Duration(seconds: 1));
      if (!disposed) {
        seconds.value++;
        return true;
      }
      return false;
    });
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Single Sink')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Only the Sink rebuilds, not the entire screen:'),
            const SizedBox(height: 24),
            Container(
              padding: const EdgeInsets.all(24),
              decoration: BoxDecoration(
                color: Colors.blue[50],
                borderRadius: BorderRadius.circular(12),
              ),
              child: Sink(
                pipe: context.read<TimerHub>().seconds,
                builder: (context, value) {
                  return Text(
                    'Timer: $value seconds',
                    style: const TextStyle(
                        fontSize: 32, fontWeight: FontWeight.bold),
                  );
                },
              ),
            ),
            const SizedBox(height: 24),
            const Text(
              'This text never rebuilds!',
              style: TextStyle(fontSize: 16, color: Colors.grey),
            ),
          ],
        ),
      ),
    );
  }
}

// ============================================================================
// EXAMPLE 5: Multiple Sinks
// ============================================================================

class MultiCounterHub extends Hub {
  late final counterA = pipe(0);
  late final counterB = pipe(0);
  late final counterC = pipe(0);

  void incrementA() => counterA.value++;
  void incrementB() => counterB.value++;
  void incrementC() => counterC.value++;
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Multiple Sinks')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Each Sink rebuilds independently:'),
            const SizedBox(height: 32),
            _CounterRow('Counter A', context.read<MultiCounterHub>().counterA,
                () {
              context.read<MultiCounterHub>().incrementA();
            }),
            const SizedBox(height: 16),
            _CounterRow('Counter B', context.read<MultiCounterHub>().counterB,
                () {
              context.read<MultiCounterHub>().incrementB();
            }),
            const SizedBox(height: 16),
            _CounterRow('Counter C', context.read<MultiCounterHub>().counterC,
                () {
              context.read<MultiCounterHub>().incrementC();
            }),
          ],
        ),
      ),
    );
  }
}

class _CounterRow extends StatelessWidget {
  final String label;
  final Pipe<int> pipe;
  final VoidCallback onIncrement;

  const _CounterRow(this.label, this.pipe, this.onIncrement);

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(label, style: const TextStyle(fontSize: 18)),
        Row(
          children: [
            Sink(
              pipe: pipe,
              builder: (context, value) => Container(
                padding:
                    const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                decoration: BoxDecoration(
                  color: Colors.green[100],
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Text('$value',
                    style: const TextStyle(
                        fontSize: 20, fontWeight: FontWeight.bold)),
              ),
            ),
            const SizedBox(width: 8),
            IconButton(
              icon: const Icon(Icons.add),
              onPressed: onIncrement,
            ),
          ],
        ),
      ],
    );
  }
}

// ============================================================================
// EXAMPLE 6: Well (Multiple Pipes)
// ============================================================================

class CalculatorHub extends Hub {
  late final a = pipe(5);
  late final b = pipe(10);
  late final operation = pipe<String>('+');

  // Computed value using computed pipe
  late final result = computedPipe<double>(
    dependencies: [a, b, operation],
    compute: () {
      switch (operation.value) {
        case '+':
          return (a.value + b.value).toDouble();
        case '-':
          return (a.value - b.value).toDouble();
        case '*':
          return (a.value * b.value).toDouble();
        case '/':
          return b.value != 0 ? a.value / b.value : 0;
        default:
          return 0;
      }
    },
  );
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Well Example')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Well listens to multiple pipes at once:'),
            const SizedBox(height: 32),
            Well(
              pipes: [
                context.read<CalculatorHub>().a,
                context.read<CalculatorHub>().b,
                context.read<CalculatorHub>().operation,
                context.read<CalculatorHub>().result,
              ],
              builder: (context) {
                final hub = context.read<CalculatorHub>();
                return Container(
                  padding: const EdgeInsets.all(24),
                  decoration: BoxDecoration(
                    color: Colors.purple[50],
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Column(
                    children: [
                      Text(
                        '${hub.a.value} ${hub.operation.value} ${hub.b.value} = ${hub.result.value.toStringAsFixed(2)}',
                        style: const TextStyle(
                            fontSize: 24, fontWeight: FontWeight.bold),
                      ),
                    ],
                  ),
                );
              },
            ),
            const SizedBox(height: 32),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: () => context.read<CalculatorHub>().a.value++,
                  child: const Text('A+'),
                ),
                ElevatedButton(
                  onPressed: () => context.read<CalculatorHub>().b.value++,
                  child: const Text('B+'),
                ),
              ],
            ),
            const SizedBox(height: 16),
            Wrap(
              spacing: 8,
              children: ['+', '-', '*', '/'].map((op) {
                return ElevatedButton(
                  onPressed: () =>
                      context.read<CalculatorHub>().operation.value = op,
                  child: Text(op),
                );
              }).toList(),
            ),
          ],
        ),
      ),
    );
  }
}

// ============================================================================
// EXAMPLE 7: HubProvider Basics
// ============================================================================

class ThemeHub extends Hub {
  late final isDark = pipe(false);

  void toggle() => isDark.value = !isDark.value;
}

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

  @override
  Widget build(BuildContext context) {
    return Sink(
      pipe: context.read<ThemeHub>().isDark,
      builder: (context, isDark) {
        return MaterialApp(
          theme: isDark ? ThemeData.dark() : ThemeData.light(),
          home: Scaffold(
            appBar: AppBar(
              title: const Text('HubProvider Example'),
              leading: IconButton(
                icon: const Icon(Icons.arrow_back),
                onPressed: () => Navigator.of(context).pop(),
              ),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    isDark ? '🌙 Dark Mode' : '☀️ Light Mode',
                    style: const TextStyle(fontSize: 32),
                  ),
                  const SizedBox(height: 24),
                  ElevatedButton(
                    onPressed: () => context.read<ThemeHub>().toggle(),
                    child: const Text('Toggle Theme'),
                  ),
                ],
              ),
            ),
          ),
        );
      },
    );
  }
}

// ============================================================================
// EXAMPLE 8: MultiHubProvider
// ============================================================================

class AuthHub extends Hub {
  late final isLoggedIn = pipe(false);
  late final username = pipe('Guest');

  void login(String name) {
    username.value = name;
    isLoggedIn.value = true;
  }

  void logout() {
    username.value = 'Guest';
    isLoggedIn.value = false;
  }
}

class SettingsHub extends Hub {
  late final fontSize = pipe(16.0);
  late final enableNotifications = pipe(true);

  void increaseFontSize() => fontSize.value += 2;
  void decreaseFontSize() => fontSize.value -= 2;
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('MultiHubProvider')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text('Multiple Hubs without nesting:',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            const SizedBox(height: 24),
            Well(
              pipes: [
                context.read<AuthHub>().isLoggedIn,
                context.read<AuthHub>().username,
              ],
              builder: (context) {
                final auth = context.read<AuthHub>();
                return Card(
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                            'Auth Status: ${auth.isLoggedIn.value ? "Logged In" : "Logged Out"}'),
                        Text('Username: ${auth.username.value}'),
                        const SizedBox(height: 8),
                        ElevatedButton(
                          onPressed: () {
                            if (auth.isLoggedIn.value) {
                              auth.logout();
                            } else {
                              auth.login('John Doe');
                            }
                          },
                          child:
                              Text(auth.isLoggedIn.value ? 'Logout' : 'Login'),
                        ),
                      ],
                    ),
                  ),
                );
              },
            ),
            const SizedBox(height: 16),
            Sink<double>(
              pipe: context.read<SettingsHub>().fontSize,
              builder: (context, fontSize) {
                return Card(
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text('Font Size: ${fontSize.toInt()}',
                            style: TextStyle(fontSize: fontSize)),
                        const SizedBox(height: 8),
                        Row(
                          children: [
                            ElevatedButton(
                              onPressed: () => context
                                  .read<SettingsHub>()
                                  .decreaseFontSize(),
                              child: const Text('A-'),
                            ),
                            const SizedBox(width: 8),
                            ElevatedButton(
                              onPressed: () => context
                                  .read<SettingsHub>()
                                  .increaseFontSize(),
                              child: const Text('A+'),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

// ============================================================================
// EXAMPLE 8.1: HubProvider.value (External Lifecycle)
// ============================================================================
//
// Use HubProvider.value when you want to manage the hub's lifecycle yourself.
// The hub is NOT automatically disposed when the provider is removed.
//
// Use cases:
// - Share a hub instance across multiple routes
// - When you need to keep state alive beyond widget lifecycle
// - Integration with existing dependency injection systems
// - Testing scenarios where you provide mock instances
// ============================================================================

class HubProviderValueExample extends StatefulWidget {
  const HubProviderValueExample({super.key});

  @override
  State<HubProviderValueExample> createState() =>
      _HubProviderValueExampleState();
}

class _HubProviderValueExampleState extends State<HubProviderValueExample> {
  int _toggleCount = 0;

  @override
  Widget build(BuildContext context) {
    return Sink(
      pipe: context.read<ThemeHub>().isDark,
      builder: (context, isDark) {
        return MaterialApp(
          theme: isDark ? ThemeData.dark() : ThemeData.light(),
          home: Scaffold(
            appBar: AppBar(
              title: const Text('HubProvider.value Example'),
              leading: IconButton(
                icon: const Icon(Icons.arrow_back),
                onPressed: () => Navigator.of(context).pop(),
              ),
            ),
            body: SingleChildScrollView(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  // Info card
                  Card(
                    child: Padding(
                      padding: const EdgeInsets.all(16),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Row(
                            children: [
                              Icon(Icons.info_outline,
                                  color: Colors.orange.shade700),
                              const SizedBox(width: 8),
                              const Text(
                                'HubProvider.value',
                                style: TextStyle(
                                  fontSize: 20,
                                  fontWeight: FontWeight.bold,
                                ),
                              ),
                            ],
                          ),
                          const SizedBox(height: 12),
                          const Text(
                            'This example uses HubProvider.value constructor.\n\n'
                            '✅ Hub was created BEFORE the provider\n'
                            '✅ You manage the lifecycle (not auto-disposed)\n'
                            '✅ Perfect for sharing state across routes\n'
                            '✅ Useful for testing with mock instances',
                            style: TextStyle(fontSize: 14),
                          ),
                        ],
                      ),
                    ),
                  ),
                  const SizedBox(height: 20),

                  // Theme toggle
                  Card(
                    child: Padding(
                      padding: const EdgeInsets.all(16),
                      child: Column(
                        children: [
                          Text(
                            isDark ? '🌙 Dark Mode' : '☀️ Light Mode',
                            style: const TextStyle(fontSize: 32),
                          ),
                          const SizedBox(height: 16),
                          Text(
                            'Toggled $_toggleCount times',
                            style: TextStyle(
                              fontSize: 16,
                              color: Colors.grey.shade600,
                            ),
                          ),
                          const SizedBox(height: 16),
                          ElevatedButton.icon(
                            onPressed: () {
                              setState(() => _toggleCount++);
                              context.read<ThemeHub>().toggle();
                            },
                            icon: const Icon(Icons.brightness_6),
                            label: const Text('Toggle Theme'),
                          ),
                        ],
                      ),
                    ),
                  ),
                  const SizedBox(height: 20),

                  // Code example
                  Card(
                    child: const Padding(
                      padding: EdgeInsets.all(16),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            '📝 How this example was created:',
                            style: TextStyle(
                              fontSize: 16,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          SizedBox(height: 12),
                          Text(
                            '// 1. Create the hub FIRST\n'
                            'final myHub = ThemeHub();\n\n'
                            '// 2. Provide it using .value\n'
                            'HubProvider<ThemeHub>.value(\n'
                            '  value: myHub,  // Pass existing instance\n'
                            '  child: MyApp(),\n'
                            ')\n\n'
                            '// 3. You must dispose it manually\n'
                            '// when you\'re done:\n'
                            'myHub.dispose();',
                            style: TextStyle(
                              fontFamily: 'monospace',
                              fontSize: 12,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                  const SizedBox(height: 20),

                  // Comparison
                  Card(
                    child: Padding(
                      padding: const EdgeInsets.all(16),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          const Text(
                            'HubProvider.create vs .value',
                            style: TextStyle(
                              fontSize: 16,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          const SizedBox(height: 12),
                          _buildComparisonRow(
                            'Lifecycle Management',
                            'Automatic',
                            'Manual',
                          ),
                          _buildComparisonRow(
                            'Auto-dispose',
                            'Yes ✅',
                            'No ❌',
                          ),
                          _buildComparisonRow(
                            'Created',
                            'By provider',
                            'Before provider',
                          ),
                          _buildComparisonRow(
                            'Use case',
                            'Most cases',
                            'Shared state',
                          ),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      },
    );
  }

  Widget _buildComparisonRow(String label, String create, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        children: [
          SizedBox(
            width: 140,
            child: Text(
              label,
              style: const TextStyle(fontWeight: FontWeight.w500),
            ),
          ),
          Expanded(
            child: Row(
              children: [
                Expanded(
                  child: Text(
                    create,
                    style: const TextStyle(fontSize: 12),
                  ),
                ),
                Expanded(
                  child: Text(
                    value,
                    style: const TextStyle(fontSize: 12),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// ============================================================================
// EXAMPLE 8.2: MultiHubProvider with Existing Instances
// ============================================================================
//
// MultiHubProvider can accept both:
// - Factory functions: () => MyHub() (will be created and disposed)
// - Hub instances: myHub (will NOT be disposed)
//
// Mix and match as needed for your use case!
// ============================================================================

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('MultiHubProvider with Values'),
        backgroundColor: Colors.teal.shade700,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Info card
            Card(
              color: Colors.teal.shade50,
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Icon(Icons.layers, color: Colors.teal.shade700),
                        const SizedBox(width: 8),
                        const Text(
                          'Pre-created Hub Instances',
                          style: TextStyle(
                            fontSize: 20,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ],
                    ),
                    const SizedBox(height: 12),
                    const Text(
                      'Both hubs in this example were created BEFORE '
                      'the MultiHubProvider:\n\n'
                      '✅ Full control over initialization\n'
                      '✅ Can configure hubs before providing them\n'
                      '✅ Easy to test with dependency injection\n'
                      '✅ You manage disposal manually',
                      style: TextStyle(fontSize: 14),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 20),

            // Auth Hub section
            Well(
              pipes: [
                context.read<AuthHub>().isLoggedIn,
                context.read<AuthHub>().username,
              ],
              builder: (context) {
                final auth = context.read<AuthHub>();
                return Card(
                  elevation: 2,
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Row(
                          children: [
                            Icon(
                              Icons.account_circle,
                              color: auth.isLoggedIn.value
                                  ? Colors.green
                                  : Colors.grey,
                            ),
                            const SizedBox(width: 8),
                            const Text(
                              'Auth Hub (Pre-created)',
                              style: TextStyle(
                                fontSize: 16,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ],
                        ),
                        const Divider(),
                        Text(
                          'Status: ${auth.isLoggedIn.value ? "Logged In ✅" : "Logged Out ❌"}',
                          style: const TextStyle(fontSize: 16),
                        ),
                        Text(
                          'User: ${auth.username.value}',
                          style: TextStyle(
                            fontSize: 14,
                            color: Colors.grey.shade700,
                          ),
                        ),
                        const SizedBox(height: 12),
                        ElevatedButton.icon(
                          onPressed: () {
                            if (auth.isLoggedIn.value) {
                              auth.logout();
                            } else {
                              auth.login('Alice');
                            }
                          },
                          icon: Icon(
                            auth.isLoggedIn.value ? Icons.logout : Icons.login,
                          ),
                          label: Text(
                            auth.isLoggedIn.value ? 'Logout' : 'Login',
                          ),
                        ),
                      ],
                    ),
                  ),
                );
              },
            ),
            const SizedBox(height: 16),

            // Settings Hub section
            Sink<double>(
              pipe: context.read<SettingsHub>().fontSize,
              builder: (context, fontSize) {
                return Card(
                  elevation: 2,
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const Row(
                          children: [
                            Icon(Icons.settings, color: Colors.blue),
                            SizedBox(width: 8),
                            Text(
                              'Settings Hub (Pre-created)',
                              style: TextStyle(
                                fontSize: 16,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ],
                        ),
                        const Divider(),
                        Text(
                          'Font Size: ${fontSize.toInt()}px',
                          style: TextStyle(fontSize: fontSize),
                        ),
                        const SizedBox(height: 12),
                        Row(
                          children: [
                            ElevatedButton.icon(
                              onPressed: () => context
                                  .read<SettingsHub>()
                                  .decreaseFontSize(),
                              icon: const Icon(Icons.text_decrease),
                              label: const Text('Smaller'),
                            ),
                            const SizedBox(width: 8),
                            ElevatedButton.icon(
                              onPressed: () => context
                                  .read<SettingsHub>()
                                  .increaseFontSize(),
                              icon: const Icon(Icons.text_increase),
                              label: const Text('Larger'),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                );
              },
            ),
            const SizedBox(height: 20),

            // Code example
            Card(
              color: Colors.grey.shade100,
              child: const Padding(
                padding: EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '📝 Code for this example:',
                      style: TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    SizedBox(height: 12),
                    Text(
                      '// 1. Create hub instances first\n'
                      'final authHub = AuthHub();\n'
                      'final settingsHub = SettingsHub();\n\n'
                      '// 2. Pass them to MultiHubProvider\n'
                      'MultiHubProvider(\n'
                      '  hubs: [\n'
                      '    authHub,        // Existing instance\n'
                      '    settingsHub,    // Existing instance\n'
                      '  ],\n'
                      '  child: MyApp(),\n'
                      ')\n\n'
                      '// You can also mix factories and values:\n'
                      'MultiHubProvider(\n'
                      '  hubs: [\n'
                      '    authHub,           // Existing (no dispose)\n'
                      '    () => ThemeHub(),  // Factory (auto-dispose)\n'
                      '  ],\n'
                      '  child: MyApp(),\n'
                      ')',
                      style: TextStyle(
                        fontFamily: 'monospace',
                        fontSize: 12,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// ============================================================================
// EXAMPLE 9: Scoped vs Global
// ============================================================================

class CounterGlobalHub extends Hub {
  late final count = pipe(0);
  void increment() => count.value++;
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Scoped vs Global')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Global Hub (survives navigation):'),
            Sink(
              pipe: context.read<CounterGlobalHub>().count,
              builder: (context, value) => Text('Global Count: $value',
                  style: const TextStyle(fontSize: 24)),
            ),
            ElevatedButton(
              onPressed: () => context.read<CounterGlobalHub>().increment(),
              child: const Text('Increment Global'),
            ),
            const SizedBox(height: 32),
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (_) => const ScopedScreen()),
                );
              },
              child: const Text('Open Scoped Screen'),
            ),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return HubProvider(
      create: () => CounterGlobalHub(), // New instance, disposed on pop
      child: Scaffold(
        appBar: AppBar(title: const Text('Scoped Screen')),
        body: Center(
          child: Comp(),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const Text('Scoped Hub (disposed on back):'),
        Sink(
          pipe: context.read<CounterGlobalHub>().count,
          builder: (context, value) => Text('Scoped Count: $value',
              style: const TextStyle(fontSize: 24)),
        ),
        ElevatedButton(
          onPressed: () => context.read<CounterGlobalHub>().increment(),
          child: const Text('Increment Scoped'),
        ),
      ],
    );
  }
}

// ============================================================================
// EXAMPLE 10: Computed Values (Computed Pipe)
// ============================================================================

class ShoppingHub extends Hub {
  late final items = pipe<List<String>>([]);
  late final pricePerItem = pipe(9.99);

  // Computed values using computed pipes
  late final itemCount = computedPipe<int>(
    dependencies: [items],
    compute: () => items.value.length,
  );

  late final total = computedPipe<double>(
    dependencies: [items, pricePerItem],
    compute: () => items.value.length * pricePerItem.value,
  );

  late final summary = computedPipe<String>(
    dependencies: [items, pricePerItem],
    compute: () =>
        '${items.value.length} items - \$${(items.value.length * pricePerItem.value).toStringAsFixed(2)}',
  );

  void addItem(String item) {
    items.value = [...items.value, item];
  }

  void clear() {
    items.value = [];
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Computed Values')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text('Use computed pipes for derived state:',
                style: TextStyle(fontSize: 16)),
            const SizedBox(height: 24),
            Well(
              pipes: [
                context.read<ShoppingHub>().itemCount,
                context.read<ShoppingHub>().pricePerItem,
                context.read<ShoppingHub>().total,
              ],
              builder: (context) {
                final hub = context.read<ShoppingHub>();
                return Card(
                  color: Colors.blue[50],
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text('Items: ${hub.itemCount.value}',
                            style: const TextStyle(fontSize: 18)),
                        Text(
                            'Price per item: \$${hub.pricePerItem.value.toStringAsFixed(2)}'),
                        const Divider(),
                        Text(
                          'Total: \$${hub.total.value.toStringAsFixed(2)}',
                          style: const TextStyle(
                              fontSize: 24, fontWeight: FontWeight.bold),
                        ),
                      ],
                    ),
                  ),
                );
              },
            ),
            const SizedBox(height: 16),
            Expanded(
              child: Sink(
                pipe: context.read<ShoppingHub>().items,
                builder: (context, items) {
                  if (items.isEmpty) {
                    return const Center(child: Text('No items yet'));
                  }
                  return ListView.builder(
                    itemCount: items.length,
                    itemBuilder: (context, index) {
                      return ListTile(
                        leading: const Icon(Icons.shopping_cart),
                        title: Text(items[index]),
                      );
                    },
                  );
                },
              ),
            ),
            Row(
              children: [
                Expanded(
                  child: ElevatedButton(
                    onPressed: () {
                      final hub = context.read<ShoppingHub>();
                      hub.addItem('Item ${hub.itemCount.value + 1}');
                    },
                    child: const Text('Add Item'),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton(
                  onPressed: () => context.read<ShoppingHub>().clear(),
                  child: const Text('Clear'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ============================================================================
// EXAMPLE 11: Async Operations (Basic AsyncPipe)
// ============================================================================
//
// Simple example showing AsyncPipe basics:
// - Loading, data, and error states
// - Pattern matching with state.when()
// - Refresh and error simulation
//
// ============================================================================

/// Simple data model
class Post {
  final int id;
  final String title;
  final String body;

  const Post({required this.id, required this.title, required this.body});
}

/// Simple async hub - no Either, no DI, just basic AsyncPipe usage
class SimpleAsyncHub extends Hub {
  // Flag to simulate error
  bool _simulateError = false;

  // AsyncPipe automatically handles loading/data/error states
  late final posts = asyncPipe<List<Post>>(
    () => _fetchPosts(),
    immediate: true, // Starts loading immediately (default)
  );

  // Simulated API call - always succeeds unless error is simulated
  Future<List<Post>> _fetchPosts() async {
    await Future.delayed(const Duration(seconds: 2));

    // Check if we should simulate an error
    if (_simulateError) {
      _simulateError = false; // Reset for next time
      throw Exception('Network error! Failed to load posts.');
    }

    return const [
      Post(
          id: 1,
          title: 'Getting Started with PipeX',
          body: 'Learn the basics of reactive state management...'),
      Post(
          id: 2,
          title: 'AsyncPipe Deep Dive',
          body: 'Handle async operations with ease...'),
      Post(
          id: 3,
          title: 'Error Handling Patterns',
          body: 'Best practices for handling errors...'),
    ];
  }

  // Simulate an error on next fetch
  void simulateError() {
    _simulateError = true;
    posts.refresh();
  }

  // Normal refresh
  void refresh() {
    _simulateError = false;
    posts.refresh();
  }
}

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

  @override
  Widget build(BuildContext context) {
    final hub = context.read<SimpleAsyncHub>();

    return Scaffold(
      appBar: AppBar(
        title: const Text('AsyncPipe Basics'),
      ),
      body: Column(
        children: [
          // Action buttons
          Container(
            padding: const EdgeInsets.all(12),
            color: Colors.grey.shade100,
            child: Row(
              children: [
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed: () => hub.refresh(),
                    icon: const Icon(Icons.refresh),
                    label: const Text('Refresh (Success)'),
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: OutlinedButton.icon(
                    onPressed: () => hub.simulateError(),
                    icon: const Icon(Icons.error_outline, color: Colors.red),
                    label: const Text('Simulate Error'),
                    style:
                        OutlinedButton.styleFrom(foregroundColor: Colors.red),
                  ),
                ),
              ],
            ),
          ),

          // Posts list with async state handling
          Expanded(
            child: Sink<AsyncValue<List<Post>>>(
              pipe: hub.posts,
              builder: (context, state) {
                // Pattern matching with state.when()
                return state.when(
                  // Loading state
                  loading: () => const Center(
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        CircularProgressIndicator(),
                        SizedBox(height: 16),
                        Text('Loading posts...'),
                      ],
                    ),
                  ),

                  // Success state
                  data: (posts) => ListView.builder(
                    padding: const EdgeInsets.all(16),
                    itemCount: posts.length + 1, // +1 for header
                    itemBuilder: (context, index) {
                      if (index == 0) {
                        return Padding(
                          padding: const EdgeInsets.only(bottom: 12),
                          child: Row(
                            children: [
                              const Icon(Icons.check_circle,
                                  color: Colors.green, size: 20),
                              const SizedBox(width: 8),
                              Text(
                                '${posts.length} posts loaded successfully',
                                style: const TextStyle(
                                    color: Colors.green,
                                    fontWeight: FontWeight.w500),
                              ),
                            ],
                          ),
                        );
                      }
                      final post = posts[index - 1];
                      return Card(
                        margin: const EdgeInsets.only(bottom: 8),
                        child: ListTile(
                          leading: CircleAvatar(child: Text('${post.id}')),
                          title: Text(post.title),
                          subtitle: Text(post.body,
                              maxLines: 2, overflow: TextOverflow.ellipsis),
                        ),
                      );
                    },
                  ),

                  // Error state
                  onError: (error, stackTrace) => Center(
                    child: Padding(
                      padding: const EdgeInsets.all(24),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          const Icon(Icons.error_outline,
                              size: 48, color: Colors.red),
                          const SizedBox(height: 16),
                          Text(
                            error.toString(),
                            textAlign: TextAlign.center,
                            style: const TextStyle(color: Colors.red),
                          ),
                          const SizedBox(height: 16),
                          ElevatedButton.icon(
                            onPressed: () => hub.refresh(),
                            icon: const Icon(Icons.refresh),
                            label: const Text('Retry'),
                          ),
                        ],
                      ),
                    ),
                  ),
                );
              },
            ),
          ),

          // Info card
          Container(
            padding: const EdgeInsets.all(12),
            color: Colors.blue.shade50,
            child: const Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('AsyncPipe Features:',
                    style:
                        TextStyle(fontWeight: FontWeight.bold, fontSize: 12)),
                SizedBox(height: 4),
                Text(
                  '• asyncPipe() - creates async reactive state\n'
                  '• state.when() - pattern match loading/data/error\n'
                  '• refresh() - re-fetch data\n'
                  '• setError() / setData() - manually set state',
                  style: TextStyle(fontSize: 11),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// ============================================================================
// EXAMPLE 12: Async Operations with Either Pattern & Service Architecture
// ============================================================================
//
// This example demonstrates a production-ready architecture with:
// 1. dartz Either<L, R> for type-safe error handling
// 2. Sealed Failure classes for domain errors
// 3. Abstract Repository interfaces
// 4. Concrete implementations (Mock/Real)
// 5. Simple Service Locator for DI
// 6. Hub consuming repository through DI
//
// Using: import 'package:dartz/dartz.dart';
//   - left(failure) creates Left<Failure, T>
//   - right(success) creates Right<Failure, T>
//   - result.fold((l) => ..., (r) => ...) pattern matches
//
// ============================================================================

// =============================================================================
// PART 1: Domain Failures (Sealed Classes)
// =============================================================================

/// Base sealed class for all domain failures
sealed class Failure {
  final String message;
  final String? code;
  final StackTrace? stackTrace;

  const Failure(this.message, {this.code, this.stackTrace});
}

/// Network connectivity failures
final class NetworkFailure extends Failure {
  final bool isTimeout;
  final bool isNoInternet;

  const NetworkFailure(
    super.message, {
    super.code,
    super.stackTrace,
    this.isTimeout = false,
    this.isNoInternet = false,
  });
}

/// Authentication/Authorization failures
final class AuthFailure extends Failure {
  final bool isTokenExpired;
  final bool isUnauthorized;
  final bool isForbidden;

  const AuthFailure(
    super.message, {
    super.code,
    super.stackTrace,
    this.isTokenExpired = false,
    this.isUnauthorized = false,
    this.isForbidden = false,
  });
}

/// Resource not found failures
final class NotFoundFailure extends Failure {
  final String? resourceType;
  final String? resourceId;

  const NotFoundFailure(
    super.message, {
    super.code,
    super.stackTrace,
    this.resourceType,
    this.resourceId,
  });
}

/// Validation/Input failures
final class ValidationFailure extends Failure {
  final Map<String, List<String>> fieldErrors;

  const ValidationFailure(
    super.message, {
    super.code,
    super.stackTrace,
    this.fieldErrors = const {},
  });
}

/// Server-side failures
final class ServerFailure extends Failure {
  final int? statusCode;
  final bool isMaintenanceMode;

  const ServerFailure(
    super.message, {
    super.code,
    super.stackTrace,
    this.statusCode,
    this.isMaintenanceMode = false,
  });
}

/// Cache/Local storage failures
final class CacheFailure extends Failure {
  const CacheFailure(super.message, {super.code, super.stackTrace});
}

/// Unknown/Unexpected failures
final class UnknownFailure extends Failure {
  final Object? originalError;

  const UnknownFailure(
    super.message, {
    super.code,
    super.stackTrace,
    this.originalError,
  });
}

// =============================================================================
// PART 3: Domain Models
// =============================================================================

class UserProfile {
  final String id;
  final String name;
  final String email;
  final String phone;
  final String city;
  final String occupation;
  final String gender;
  final int age;

  const UserProfile({
    required this.id,
    required this.name,
    required this.email,
    required this.phone,
    required this.city,
    required this.occupation,
    required this.gender,
    required this.age,
  });

  UserProfile copyWith({
    String? id,
    String? name,
    String? email,
    String? phone,
    String? city,
    String? occupation,
    String? gender,
    int? age,
  }) {
    return UserProfile(
      id: id ?? this.id,
      name: name ?? this.name,
      email: email ?? this.email,
      phone: phone ?? this.phone,
      city: city ?? this.city,
      occupation: occupation ?? this.occupation,
      gender: gender ?? this.gender,
      age: age ?? this.age,
    );
  }
}

// =============================================================================
// PART 4: Abstract Repository Interface
// =============================================================================

/// Abstract repository interface - defines the contract
abstract class IUserRepository {
  /// Fetch user profile by ID
  Future<Either<Failure, UserProfile>> getUserProfile(String userId);

  /// Update user profile
  Future<Either<Failure, UserProfile>> updateUserProfile(UserProfile profile);

  /// Delete user profile
  Future<Either<Failure, void>> deleteUserProfile(String userId);
}

// =============================================================================
// PART 5: Concrete Repository Implementations
// =============================================================================

/// Scenario enum for demo purposes
enum MockScenario {
  success('Success', Icons.check_circle, Colors.green),
  networkError('Network Error', Icons.wifi_off, Colors.orange),
  authError('Auth Error', Icons.lock, Colors.red),
  notFound('Not Found', Icons.search_off, Colors.purple),
  validationError('Validation', Icons.warning, Colors.amber),
  serverError('Server Error', Icons.cloud_off, Colors.grey);

  final String label;
  final IconData icon;
  final Color color;

  const MockScenario(this.label, this.icon, this.color);
}

/// Mock repository implementation for testing/demo
class MockUserRepository implements IUserRepository {
  MockScenario scenario;

  MockUserRepository({this.scenario = MockScenario.success});

  @override
  Future<Either<Failure, UserProfile>> getUserProfile(String userId) async {
    // Simulate network delay
    await Future.delayed(const Duration(seconds: 2));

    // Return based on scenario using dartz's left() and right() functions
    return switch (scenario) {
      MockScenario.success => right(UserProfile(
          id: userId,
          name: 'John Doe',
          email: '[email protected]',
          phone: '+1 (555) 123-4567',
          city: 'San Francisco',
          occupation: 'Software Engineer',
          gender: 'Male',
          age: 28,
        )),
      MockScenario.networkError => left(const NetworkFailure(
          'Unable to connect. Check your internet connection.',
          code: 'NETWORK_ERROR',
          isNoInternet: true,
        )),
      MockScenario.authError => left(const AuthFailure(
          'Session expired. Please log in again.',
          code: 'TOKEN_EXPIRED',
          isTokenExpired: true,
        )),
      MockScenario.notFound => left(NotFoundFailure(
          'User profile not found.',
          code: 'USER_NOT_FOUND',
          resourceType: 'UserProfile',
          resourceId: userId,
        )),
      MockScenario.validationError => left(const ValidationFailure(
          'Invalid request parameters.',
          code: 'VALIDATION_ERROR',
          fieldErrors: {
            'userId': ['Must be a valid UUID format'],
          },
        )),
      MockScenario.serverError => left(const ServerFailure(
          'Internal server error. Please try again later.',
          code: 'INTERNAL_ERROR',
          statusCode: 500,
        )),
    };
  }

  @override
  Future<Either<Failure, UserProfile>> updateUserProfile(
      UserProfile profile) async {
    await Future.delayed(const Duration(milliseconds: 500));
    return right(profile);
  }

  @override
  Future<Either<Failure, void>> deleteUserProfile(String userId) async {
    await Future.delayed(const Duration(milliseconds: 500));
    return right(null);
  }
}

// =============================================================================
// PART 6: Simple Service Locator (DI)
// =============================================================================

/// Simple service locator for dependency injection
class ServiceLocator {
  static final ServiceLocator _instance = ServiceLocator._();
  static ServiceLocator get instance => _instance;

  ServiceLocator._();

  final Map<Type, Object> _services = {};

  /// Register a service
  void register<T extends Object>(T service) {
    _services[T] = service;
  }

  /// Get a registered service
  T get<T extends Object>() {
    final service = _services[T];
    if (service == null) {
      throw Exception('Service $T not registered');
    }
    return service as T;
  }

  /// Check if service is registered
  bool isRegistered<T extends Object>() => _services.containsKey(T);

  /// Clear all services
  void reset() => _services.clear();
}

// Initialize services (call this at app startup)
void setupServices() {
  final locator = ServiceLocator.instance;

  // Register mock repository (in production, use real implementation)
  locator.register<IUserRepository>(MockUserRepository());
}

// =============================================================================
// PART 7: Hub with Repository Integration
// =============================================================================

class DataHub extends Hub {
  // Get repository from service locator
  final IUserRepository _repository;

  // Expose scenario for demo UI
  late final selectedScenario = pipe<MockScenario>(MockScenario.success);

  // AsyncPipe for user profile - uses Either pattern internally
  late final userProfile = asyncPipe<UserProfile>(
    () => _fetchProfile(),
  );

  DataHub({IUserRepository? repository})
      : _repository =
            repository ?? ServiceLocator.instance.get<IUserRepository>();

  /// Fetch profile using Either pattern
  Future<UserProfile> _fetchProfile() async {
    // Update mock scenario if using mock repository
    final repo = _repository;
    if (repo is MockUserRepository) {
      repo.scenario = selectedScenario.value;
    }

    // Call repository - returns Either<Failure, UserProfile>
    final result = await _repository.getUserProfile('USR-12345');

    // Fold the Either to either throw or return value
    return result.fold(
      (failure) => throw failure, // Convert Left to exception for AsyncPipe
      (profile) => profile, // Return Right value
    );
  }

  /// Retry with different scenario
  void retryWithScenario(MockScenario scenario) {
    selectedScenario.value = scenario;
    userProfile.refresh();
  }

  /// Update gender with optimistic update
  void updateGender(String newGender) {
    final current = userProfile.dataOrNull;
    if (current != null) {
      userProfile.setData(current.copyWith(gender: newGender));
    }
  }

  /// Update age with optimistic update
  void updateAge(int newAge) {
    final current = userProfile.dataOrNull;
    if (current != null) {
      userProfile.setData(current.copyWith(age: newAge));
    }
  }
}

// =============================================================================
// PART 8: UI Widgets
// =============================================================================

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

  @override
  Widget build(BuildContext context) {
    final hub = context.read<DataHub>();

    return Scaffold(
      appBar: AppBar(
        title: const Text('Either Pattern + DI'),
        backgroundColor: Colors.indigo,
      ),
      body: Column(
        children: [
          // Architecture info banner
          Container(
            width: double.infinity,
            padding: const EdgeInsets.all(12),
            color: Colors.indigo.shade50,
            child: const Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Clean Architecture Demo',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13),
                ),
                SizedBox(height: 4),
                Text(
                  'Either<Failure, T> • Abstract Repository • Service Locator',
                  style: TextStyle(fontSize: 11, color: Colors.indigo),
                ),
              ],
            ),
          ),

          // Scenario selector
          Container(
            padding: const EdgeInsets.all(12),
            color: Colors.grey[100],
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  'Mock Repository Response:',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
                ),
                const SizedBox(height: 8),
                Sink<MockScenario>(
                  pipe: hub.selectedScenario,
                  builder: (context, selected) {
                    return Wrap(
                      spacing: 6,
                      runSpacing: 6,
                      children: MockScenario.values.map((scenario) {
                        return ChoiceChip(
                          avatar: Icon(scenario.icon,
                              size: 16, color: scenario.color),
                          label: Text(scenario.label,
                              style: const TextStyle(fontSize: 11)),
                          selected: selected == scenario,
                          onSelected: (_) => hub.retryWithScenario(scenario),
                          selectedColor: scenario.color.withOpacity(0.2),
                        );
                      }).toList(),
                    );
                  },
                ),
              ],
            ),
          ),

          // Main content
          Expanded(
            child: Sink<AsyncValue<UserProfile>>(
              pipe: hub.userProfile,
              builder: (context, state) {
                return state.when(
                  loading: () => const _LoadingWidget(),
                  data: (profile) => _ProfileScreen(hub: hub, profile: profile),
                  onError: (error, _) =>
                      _FailureWidget(failure: error, hub: hub),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

class _LoadingWidget extends StatelessWidget {
  const _LoadingWidget();

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          CircularProgressIndicator(),
          SizedBox(height: 16),
          Text('Fetching from repository...', style: TextStyle(fontSize: 14)),
        ],
      ),
    );
  }
}

// =============================================================================
// PART 9: Failure Widget with Pattern Matching
// =============================================================================

class _FailureWidget extends StatelessWidget {
  final Object failure;
  final DataHub hub;

  const _FailureWidget({required this.failure, required this.hub});

  @override
  Widget build(BuildContext context) {
    // Pattern match on sealed Failure type!
    return switch (failure) {
      NetworkFailure f => _buildFailureCard(
          context,
          icon: Icons.wifi_off,
          color: Colors.orange,
          title: 'Network Failure',
          message: f.message,
          code: f.code,
          details: f.isNoInternet
              ? '📡 No internet connection'
              : f.isTimeout
                  ? '⏱️ Request timed out'
                  : null,
        ),
      AuthFailure f => _buildFailureCard(
          context,
          icon: Icons.lock_outline,
          color: Colors.red,
          title: 'Auth Failure',
          message: f.message,
          code: f.code,
          details: f.isTokenExpired ? '🔑 Token expired' : null,
          primaryAction: 'Log In Again',
          primaryIcon: Icons.login,
        ),
      NotFoundFailure f => _buildFailureCard(
          context,
          icon: Icons.search_off,
          color: Colors.purple,
          title: 'Not Found',
          message: f.message,
          code: f.code,
          details: f.resourceType != null
              ? '📦 ${f.resourceType}: ${f.resourceId}'
              : null,
        ),
      ValidationFailure f => _buildValidationFailure(context, f),
      ServerFailure f => _buildFailureCard(
          context,
          icon: Icons.cloud_off,
          color: Colors.grey,
          title: 'Server Failure',
          message: f.message,
          code: f.code,
          details: f.statusCode != null ? '🔢 HTTP ${f.statusCode}' : null,
        ),
      CacheFailure f => _buildFailureCard(
          context,
          icon: Icons.storage,
          color: Colors.brown,
          title: 'Cache Failure',
          message: f.message,
          code: f.code,
        ),
      UnknownFailure f => _buildFailureCard(
          context,
          icon: Icons.error_outline,
          color: Colors.red,
          title: 'Unknown Failure',
          message: f.message,
          code: f.code,
        ),
      _ => _buildFailureCard(
          context,
          icon: Icons.error,
          color: Colors.red,
          title: 'Error',
          message: failure.toString(),
        ),
    };
  }

  Widget _buildFailureCard(
    BuildContext context, {
    required IconData icon,
    required Color color,
    required String title,
    required String message,
    String? code,
    String? details,
    String? primaryAction,
    IconData? primaryIcon,
  }) {
    return Center(
      child: SingleChildScrollView(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                color: color.withOpacity(0.1),
                shape: BoxShape.circle,
              ),
              child: Icon(icon, size: 48, color: color),
            ),
            const SizedBox(height: 16),
            Text(
              title,
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
                color: color,
              ),
            ),
            const SizedBox(height: 8),
            Text(
              message,
              textAlign: TextAlign.center,
              style: const TextStyle(fontSize: 14),
            ),
            if (code != null) ...[
              const SizedBox(height: 8),
              Container(
                padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                decoration: BoxDecoration(
                  color: Colors.grey[200],
                  borderRadius: BorderRadius.circular(4),
                ),
                child: Text(
                  'Code: $code',
                  style: TextStyle(
                    fontSize: 11,
                    fontFamily: 'monospace',
                    color: Colors.grey[700],
                  ),
                ),
              ),
            ],
            if (details != null) ...[
              const SizedBox(height: 8),
              Text(details,
                  style: TextStyle(fontSize: 12, color: Colors.grey[600])),
            ],
            const SizedBox(height: 24),
            Wrap(
              spacing: 12,
              runSpacing: 8,
              alignment: WrapAlignment.center,
              children: [
                ElevatedButton.icon(
                  onPressed: () => hub.userProfile.refresh(),
                  icon: Icon(primaryIcon ?? Icons.refresh),
                  label: Text(primaryAction ?? 'Retry'),
                ),
                OutlinedButton.icon(
                  onPressed: () => hub.retryWithScenario(MockScenario.success),
                  icon: const Icon(Icons.check),
                  label: const Text('Mock Success'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildValidationFailure(BuildContext context, ValidationFailure f) {
    return Center(
      child: SingleChildScrollView(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                color: Colors.amber.withOpacity(0.1),
                shape: BoxShape.circle,
              ),
              child: const Icon(Icons.warning_amber,
                  size: 48, color: Colors.amber),
            ),
            const SizedBox(height: 16),
            const Text(
              'Validation Failure',
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
                color: Colors.amber,
              ),
            ),
            const SizedBox(height: 8),
            Text(f.message, textAlign: TextAlign.center),
            if (f.fieldErrors.isNotEmpty) ...[
              const SizedBox(height: 16),
              Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Colors.amber.shade50,
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.amber.shade200),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('Field Errors:',
                        style: TextStyle(fontWeight: FontWeight.bold)),
                    const SizedBox(height: 8),
                    ...f.fieldErrors.entries.map((e) => Padding(
                          padding: const EdgeInsets.only(bottom: 4),
                          child: Row(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text('• ${e.key}: ',
                                  style: const TextStyle(
                                      fontWeight: FontWeight.w600)),
                              Expanded(child: Text(e.value.join(', '))),
                            ],
                          ),
                        )),
                  ],
                ),
              ),
            ],
            const SizedBox(height: 24),
            OutlinedButton.icon(
              onPressed: () => hub.retryWithScenario(MockScenario.success),
              icon: const Icon(Icons.check),
              label: const Text('Fix & Retry'),
            ),
          ],
        ),
      ),
    );
  }
}

// =============================================================================
// PART 10: Profile Screen
// =============================================================================

class _ProfileScreen extends StatelessWidget {
  final DataHub hub;
  final UserProfile profile;

  const _ProfileScreen({required this.hub, required this.profile});

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Success banner
          Container(
            padding: const EdgeInsets.all(12),
            decoration: BoxDecoration(
              color: Colors.green.shade50,
              borderRadius: BorderRadius.circular(8),
              border: Border.all(color: Colors.green.shade200),
            ),
            child: const Row(
              children: [
                Icon(Icons.check_circle, size: 20, color: Colors.green),
                SizedBox(width: 8),
                Expanded(
                  child: Text(
                    'Either.Right(UserProfile) returned successfully!',
                    style: TextStyle(fontSize: 12, color: Colors.green),
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),

          // Profile fields
          _buildField('ID', profile.id, Icons.badge),
          _buildField('Name', profile.name, Icons.person),
          _buildField('Email', profile.email, Icons.email),
          _buildField('Phone', profile.phone, Icons.phone),
          _buildField('City', profile.city, Icons.location_city),
          _buildField('Occupation', profile.occupation, Icons.work),

          const SizedBox(height: 16),

          // Gender
          const Text('Gender:', style: TextStyle(fontWeight: FontWeight.w600)),
          const SizedBox(height: 8),
          Row(
            children: ['Male', 'Female', 'Other']
                .map((g) => Padding(
                      padding: const EdgeInsets.only(right: 8),
                      child: ChoiceChip(
                        label: Text(g),
                        selected: profile.gender == g,
                        onSelected: (_) => hub.updateGender(g),
                      ),
                    ))
                .toList(),
          ),
          const SizedBox(height: 16),

          // Age
          Row(
            children: [
              const Text('Age:', style: TextStyle(fontWeight: FontWeight.w600)),
              const SizedBox(width: 12),
              Text('${profile.age}',
                  style: const TextStyle(
                      fontSize: 24, fontWeight: FontWeight.bold)),
              IconButton(
                  icon: const Icon(Icons.remove_circle),
                  onPressed: () {
                    if (profile.age > 1) hub.updateAge(profile.age - 1);
                  }),
              IconButton(
                  icon: const Icon(Icons.add_circle),
                  onPressed: () => hub.updateAge(profile.age + 1)),
            ],
          ),

          const SizedBox(height: 24),

          // Architecture info
          Container(
            padding: const EdgeInsets.all(12),
            decoration: BoxDecoration(
              color: Colors.indigo.shade50,
              borderRadius: BorderRadius.circular(8),
            ),
            child: const Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('📚 Architecture Pattern:',
                    style: TextStyle(fontWeight: FontWeight.bold)),
                SizedBox(height: 8),
                Text(
                  '• Either<Failure, T> for type-safe errors\n'
                  '• Sealed Failure classes for pattern matching\n'
                  '• Abstract IUserRepository interface\n'
                  '• MockUserRepository implementation\n'
                  '• ServiceLocator for dependency injection\n'
                  '• Hub consumes repository via DI',
                  style: TextStyle(fontSize: 12),
                ),
              ],
            ),
          ),

          const SizedBox(height: 16),
          Center(
            child: ElevatedButton.icon(
              onPressed: () => hub.userProfile.refresh(),
              icon: const Icon(Icons.refresh),
              label: const Text('Refresh'),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildField(String label, String value, IconData icon) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Row(
        children: [
          Icon(icon, size: 18, color: Colors.grey),
          const SizedBox(width: 12),
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(label,
                  style: TextStyle(fontSize: 11, color: Colors.grey[600])),
              Text(value, style: const TextStyle(fontSize: 14)),
            ],
          ),
        ],
      ),
    );
  }
}

// ============================================================================
// EXAMPLE 12: Form Management
// ============================================================================

class FormHub extends Hub {
  late final name = pipe('');
  late final email = pipe('');
  late final age = pipe('');

  // Validation computed pipes
  late final isNameValid = computedPipe<bool>(
    dependencies: [name],
    compute: () => name.value.length >= 3,
  );

  late final isEmailValid = computedPipe<bool>(
    dependencies: [email],
    compute: () => email.value.contains('@'),
  );

  late final isAgeValid = computedPipe<bool>(
    dependencies: [age],
    compute: () =>
        int.tryParse(age.value) != null && int.parse(age.value) >= 18,
  );

  late final isFormValid = computedPipe<bool>(
    dependencies: [name, email, age],
    compute: () =>
        name.value.length >= 3 &&
        email.value.contains('@') &&
        (int.tryParse(age.value) != null && int.parse(age.value) >= 18),
  );

  void submit() {
    if (isFormValid.value) {
      // Handle submission
    }
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Form Management')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              decoration:
                  const InputDecoration(labelText: 'Name (min 3 chars)'),
              onChanged: (value) => context.read<FormHub>().name.value = value,
            ),
            Sink<bool>(
              pipe: context.read<FormHub>().isNameValid,
              builder: (context, isValid) {
                return Text(
                  isValid ? '✓ Valid' : '✗ Too short',
                  style: TextStyle(
                      color: isValid ? Colors.green : Colors.red, fontSize: 12),
                );
              },
            ),
            const SizedBox(height: 16),
            TextField(
              decoration: const InputDecoration(labelText: 'Email'),
              onChanged: (value) => context.read<FormHub>().email.value = value,
            ),
            Sink<bool>(
              pipe: context.read<FormHub>().isEmailValid,
              builder: (context, isValid) {
                return Text(
                  isValid ? '✓ Valid' : '✗ Invalid email',
                  style: TextStyle(
                      color: isValid ? Colors.green : Colors.red, fontSize: 12),
                );
              },
            ),
            const SizedBox(height: 16),
            TextField(
              decoration: const InputDecoration(labelText: 'Age (18+)'),
              keyboardType: TextInputType.number,
              onChanged: (value) => context.read<FormHub>().age.value = value,
            ),
            Sink<bool>(
              pipe: context.read<FormHub>().isAgeValid,
              builder: (context, isValid) {
                return Text(
                  isValid ? '✓ Valid' : '✗ Must be 18+',
                  style: TextStyle(
                      color: isValid ? Colors.green : Colors.red, fontSize: 12),
                );
              },
            ),
            const SizedBox(height: 24),
            Sink<bool>(
              pipe: context.read<FormHub>().isFormValid,
              builder: (context, isValid) {
                final hub = context.read<FormHub>();
                return ElevatedButton(
                  onPressed: isValid ? () => hub.submit() : null,
                  child: const Text('Submit'),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

// ============================================================================
// EXAMPLE 13: Class Type in Pipe
// ============================================================================

/// User profile model class (MUTABLE)
/// This class demonstrates using mutable objects with Pipe.forceUpdate()
class MutableUserProfile {
  String name;
  int age;
  String email;
  String bio;
  bool isPremium;

  MutableUserProfile({
    required this.name,
    required this.age,
    required this.email,
    required this.bio,
    required this.isPremium,
  });
}

class UserProfileHub extends Hub {
  late final user = pipe<MutableUserProfile>(
    MutableUserProfile(
      name: 'John Doe',
      age: 28,
      email: '[email protected]',
      bio: 'Flutter developer',
      isPremium: false,
    ),
  );

  /// Update specific fields by mutating the object and calling forceUpdate
  /// This demonstrates using mutable objects with Pipe
  void updateName(String name) {
    user.value.name = name;
    user.pump(user.value); // Force rebuild even though reference didn't change
  }

  void updateAge(int age) {
    user.value.age++;
    user.pump(user.value);
  }

  void updateEmail(String email) {
    user.value.email = email;
    user.pump(user.value);
  }

  void updateBio(String bio) {
    user.value.bio = bio;
    user.pump(user.value);
  }

  void togglePremium() {
    user.value.isPremium = !user.value.isPremium;
    user.pump(user.value);
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Class Type in Pipe')),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              'Using MUTABLE classes in Pipes:',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            const Text(
              'Entire object is stored in one Pipe. Mutate fields and call forceUpdate().',
              style: TextStyle(fontSize: 14, color: Colors.grey),
            ),
            const SizedBox(height: 24),

            // Display user profile
            Sink(
              pipe: context.read<UserProfileHub>().user,
              builder: (context, user) {
                return Card(
                  elevation: 4,
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Row(
                          children: [
                            CircleAvatar(
                              radius: 30,
                              backgroundColor:
                                  user.isPremium ? Colors.amber : Colors.grey,
                              child: Text(
                                user.name.isNotEmpty ? user.name[0] : '?',
                                style: const TextStyle(
                                    fontSize: 24, color: Colors.white),
                              ),
                            ),
                            const SizedBox(width: 16),
                            Expanded(
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Row(
                                    children: [
                                      Text(
                                        user.name,
                                        style: const TextStyle(
                                          fontSize: 20,
                                          fontWeight: FontWeight.bold,
                                        ),
                                      ),
                                      if (user.isPremium)
                                        const Padding(
                                          padding: EdgeInsets.only(left: 8),
                                          child: Icon(Icons.star,
                                              color: Colors.amber, size: 20),
                                        ),
                                    ],
                                  ),
                                  Text(
                                    '${user.age} years old',
                                    style: const TextStyle(color: Colors.grey),
                                  ),
                                ],
                              ),
                            ),
                          ],
                        ),
                        const Divider(height: 24),
                        Row(
                          children: [
                            const Icon(Icons.email,
                                size: 16, color: Colors.grey),
                            const SizedBox(width: 8),
                            Text(user.email),
                          ],
                        ),
                        const SizedBox(height: 8),
                        Row(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Icon(Icons.info,
                                size: 16, color: Colors.grey),
                            const SizedBox(width: 8),
                            Expanded(child: Text(user.bio)),
                          ],
                        ),
                      ],
                    ),
                  ),
                );
              },
            ),

            const SizedBox(height: 32),
            const Text(
              'Update Methods:',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),

            // Update name
            TextField(
              decoration: const InputDecoration(
                labelText: 'Name',
                border: OutlineInputBorder(),
              ),
              onChanged: (value) {
                context.read<UserProfileHub>().updateName(value);
              },
            ),
            const SizedBox(height: 12),

            // Update email
            TextField(
              decoration: const InputDecoration(
                labelText: 'Email',
                border: OutlineInputBorder(),
              ),
              onChanged: (value) {
                context.read<UserProfileHub>().updateEmail(value);
              },
            ),
            const SizedBox(height: 12),

            // Update bio
            TextField(
              decoration: const InputDecoration(
                labelText: 'Bio',
                border: OutlineInputBorder(),
              ),
              maxLines: 2,
              onChanged: (value) {
                context.read<UserProfileHub>().updateBio(value);
              },
            ),
            const SizedBox(height: 24),

            // Age buttons
            Row(
              children: [
                const Text('Age: '),
                IconButton(
                  icon: const Icon(Icons.remove),
                  onPressed: () {
                    final hub = context.read<UserProfileHub>();
                    final newAge = (hub.user.value.age - 1).clamp(0, 120);
                    hub.updateAge(newAge);
                  },
                ),
                Sink(
                  pipe: context.read<UserProfileHub>().user,
                  builder: (context, user) => Text(
                    '${user.age}',
                    style: const TextStyle(
                        fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                ),
                IconButton(
                  icon: const Icon(Icons.add),
                  onPressed: () {
                    final hub = context.read<UserProfileHub>();
                    final newAge = (hub.user.value.age + 1).clamp(0, 120);
                    hub.updateAge(newAge);
                  },
                ),
              ],
            ),
            const SizedBox(height: 16),

            // Premium toggle
            ElevatedButton.icon(
              onPressed: () => context.read<UserProfileHub>().togglePremium(),
              icon: const Icon(Icons.star),
              label: const Text('Toggle Premium Status'),
              style: ElevatedButton.styleFrom(
                minimumSize: const Size(double.infinity, 48),
              ),
            ),

            const SizedBox(height: 32),
            Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Colors.blue[50],
                borderRadius: BorderRadius.circular(8),
              ),
              child: const Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    '💡 Key Points:',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  SizedBox(height: 8),
                  Text('• Entire object stored in Pipe<UserProfile>'),
                  Text('• Mutate object fields directly'),
                  Text('• Call forceUpdate() to trigger Sink rebuild'),
                  Text('• Useful for mutable objects (e.g., from APIs)'),
                  Text('• Reference stays same, but UI updates'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// ============================================================================
// EXAMPLE: HubListener - Conditional Side Effects
// ============================================================================

class TargetCounterHub extends Hub {
  late final count = pipe(0);
  late final target = pipe(5);
  late final VoidCallback _removeListener;

  void onTargetReached(BuildContext context) {
    // _removeListener is a function that removes the listener from the hub.
    // You may or may not call it in dispose, it's up to you.
    // If you don't call it in dispose, the listener will be also disposed when the hub is disposed.
    _removeListener = addListener(() {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('🎯 Target Reached!'),
          backgroundColor: Colors.green,
          duration: Duration(seconds: 2),
        ),
      );
    });
  }

  void increment() => count.value++;
  void decrement() => count.value--;
  void setTarget(int value) => target.value = value;

  @override
  void dispose() {
    _removeListener();
    super.dispose();
  }
}

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

  @override
  Widget build(BuildContext context) {
    final hub = context.read<TargetCounterHub>();
    hub.onTargetReached(context);
    return Scaffold(
      appBar: AppBar(
        title: const Text('HubListener Example'),
        backgroundColor: Colors.purple,
      ),
      body: HubListener<TargetCounterHub>(
        listenWhen: (hub) {
          // Condition: when count equals target
          return hub.count.value == hub.target.value;
        },
        onConditionMet: () {
          // Side effect: show snackbar (NO rebuild!)
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(
              content: Text('🎯🎯🎯 Target Reached!'),
              backgroundColor: Colors.green,
              duration: Duration(seconds: 2),
            ),
          );
        },
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                '🎯 Target Counter',
                style: TextStyle(
                  fontSize: 28,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 40),

              // Current count
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text(
                    'Count: ',
                    style: TextStyle(fontSize: 20),
                  ),
                  Sink<int>(
                    pipe: hub.count,
                    builder: (context, count) => Text(
                      count.toString(),
                      style: const TextStyle(
                        fontSize: 48,
                        fontWeight: FontWeight.bold,
                        color: Colors.blue,
                      ),
                    ),
                  ),
                ],
              ),

              const SizedBox(height: 20),

              // Target value
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text(
                    'Target: ',
                    style: TextStyle(fontSize: 20),
                  ),
                  Sink<int>(
                    pipe: hub.target,
                    builder: (context, target) => Text(
                      target.toString(),
                      style: const TextStyle(
                        fontSize: 48,
                        fontWeight: FontWeight.bold,
                        color: Colors.purple,
                      ),
                    ),
                  ),
                ],
              ),

              const SizedBox(height: 40),

              // Counter buttons
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton(
                    onPressed: hub.decrement,
                    style: ElevatedButton.styleFrom(
                      padding: const EdgeInsets.all(20),
                      backgroundColor: Colors.red,
                      foregroundColor: Colors.white,
                    ),
                    child: const Icon(Icons.remove, size: 32),
                  ),
                  const SizedBox(width: 20),
                  ElevatedButton(
                    onPressed: hub.increment,
                    style: ElevatedButton.styleFrom(
                      padding: const EdgeInsets.all(20),
                      backgroundColor: Colors.green,
                      foregroundColor: Colors.white,
                    ),
                    child: const Icon(Icons.add, size: 32),
                  ),
                ],
              ),

              const SizedBox(height: 40),

              // Target selection
              const Text(
                'Set Target:',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 12),
              Wrap(
                spacing: 8,
                runSpacing: 8,
                alignment: WrapAlignment.center,
                children: [3, 5, 10, 15, 20].map((value) {
                  return Sink<int>(
                    pipe: hub.target,
                    builder: (context, currentTarget) => OutlinedButton(
                      onPressed: () => hub.setTarget(value),
                      style: OutlinedButton.styleFrom(
                        backgroundColor: currentTarget == value
                            ? Colors.purple.withOpacity(0.2)
                            : null,
                        side: BorderSide(
                          color: currentTarget == value
                              ? Colors.purple
                              : Colors.grey,
                          width: 2,
                        ),
                      ),
                      child: Text(
                        value.toString(),
                        style: TextStyle(
                          fontSize: 18,
                          fontWeight: currentTarget == value
                              ? FontWeight.bold
                              : FontWeight.normal,
                        ),
                      ),
                    ),
                  );
                }).toList(),
              ),

              const Spacer(),

              // Info box
              Container(
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: Colors.blue[50],
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.blue[200]!),
                ),
                child: const Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '💡 How HubListener works:',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    SizedBox(height: 8),
                    Text('• Monitors ALL pipes in the hub'),
                    Text('• listenWhen: checks condition on every change'),
                    Text('• onConditionMet: executes when condition is true'),
                    Text('• Child widget NEVER rebuilds'),
                    Text('• Perfect for: snackbars, dialogs, navigation'),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
23
likes
160
points
334
downloads
screenshot

Publisher

verified publishernavaneethk.dev

Weekly Downloads

A lightweight, reactive state management library for Flutter with fine-grained reactivity and minimal boilerplate.

Repository (GitHub)
View/report issues

Topics

#state-management #reactive #flutter #signals #controller

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on pipe_x