aegis_honeycomb 1.0.2 copy "aegis_honeycomb: ^1.0.2" to clipboard
aegis_honeycomb: ^1.0.2 copied to clipboard

A simple, type-safe, codegen-free Flutter state management library with clear State/Effect separation, auto dependency tracking, and powerful Scope/Override mechanism.

example/lib/main.dart

import 'package:aegis_honeycomb/honeycomb.dart';
import 'package:flutter/material.dart';

void main() {
  // 启用诊断日志 - 使用 PrintLogger 直接输出到终端
  HoneycombDiagnostics.instance.enableLogging(
    customLogger: PrintLogger(),
    level: LogLevel.info,
  );
  HoneycombDiagnostics.instance.enabled=true;
  runApp(HoneycombScope(container: HoneycombContainer(), child: const MyApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Honeycomb Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.amber),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('🍯 Honeycomb Demo'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: ListView(
        children: [
          _DemoTile(
            title: 'Counter',
            subtitle: '基础 StateRef + Computed',
            icon: Icons.add_circle,
            onTap: () => _push(context, const CounterDemo()),
          ),
          _DemoTile(
            title: 'Todo List',
            subtitle: 'Selector + 批量更新',
            icon: Icons.checklist,
            onTap: () => _push(context, const TodoDemo()),
          ),
          _DemoTile(
            title: 'Async Data',
            subtitle: 'AsyncComputed + AsyncValue',
            icon: Icons.cloud_download,
            onTap: () => _push(context, const AsyncDemo()),
          ),
          _DemoTile(
            title: 'Effects',
            subtitle: 'Toast / Navigation 事件',
            icon: Icons.notifications,
            onTap: () => _push(context, const EffectDemo()),
          ),
          _DemoTile(
            title: 'Scope Override',
            subtitle: '局部状态覆盖',
            icon: Icons.layers,
            onTap: () => _push(context, const ScopeDemo()),
          ),
          _DemoTile(
            title: 'Form Validation',
            subtitle: 'SafeComputed 错误处理',
            icon: Icons.edit_note,
            onTap: () => _push(context, const FormDemo()),
          ),
        ],
      ),
    );
  }

  void _push(BuildContext context, Widget page) {
    Navigator.push(context, MaterialPageRoute(builder: (_) => page));
  }
}

class _DemoTile extends StatelessWidget {
  const _DemoTile({
    required this.title,
    required this.subtitle,
    required this.icon,
    required this.onTap,
  });

  final String title;
  final String subtitle;
  final IconData icon;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      leading: Icon(icon, color: Colors.amber),
      title: Text(title),
      subtitle: Text(subtitle),
      trailing: const Icon(Icons.chevron_right),
      onTap: onTap,
    );
  }
}

// ============================================================
// Demo 1: Counter - 基础 StateRef + Computed
// ============================================================

final counterState = StateRef(0);
final doubleCounter = Computed((watch) => watch(counterState) * 2);
final isEven = Computed((watch) => watch(counterState) % 2 == 0);

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter Demo')),
      body: Center(
        child: HoneycombConsumer(
          builder: (context, ref, _) {
            final count = ref.watch(counterState);
            final doubled = ref.watch(doubleCounter);
            final even = ref.watch(isEven);

            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('Count: $count', style: const TextStyle(fontSize: 48)),
                const SizedBox(height: 16),
                Text('Doubled: $doubled', style: const TextStyle(fontSize: 24)),
                const SizedBox(height: 8),
                Chip(
                  label: Text(even ? 'Even' : 'Odd'),
                  backgroundColor:
                      even ? Colors.green[100] : Colors.orange[100],
                ),
              ],
            );
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          FloatingActionButton(
            heroTag: 'add',
            onPressed: () {
              final container = HoneycombScope.readOf(context);
              container.write(counterState, container.read(counterState) + 1);
            },
            child: const Icon(Icons.add),
          ),
          const SizedBox(height: 8),
          FloatingActionButton(
            heroTag: 'sub',
            onPressed: () {
              final container = HoneycombScope.readOf(context);
              container.write(counterState, container.read(counterState) - 1);
            },
            child: const Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

// ============================================================
// Demo 2: Todo List - Selector + 批量更新
// ============================================================

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

  Todo copyWith({String? title, bool? completed}) {
    return Todo(
      id,
      title ?? this.title,
      completed: completed ?? this.completed,
    );
  }
}

final todosState = StateRef<List<Todo>>([
  Todo(1, 'Learn Honeycomb'),
  Todo(2, 'Build awesome app'),
  Todo(3, 'Deploy to production'),
]);

// Selector: 只监听列表长度
final todoCount = Computed((watch) => watch(todosState).length);

// Selector: 只监听完成数量
final completedCount = Computed(
  (watch) => watch(todosState).where((t) => t.completed).length,
);

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Todo Demo'),
        actions: [
          // 显示统计,使用 Selector 优化
          HoneycombConsumer(
            builder: (context, ref, _) {
              final total = ref.watch(todoCount);
              final done = ref.watch(completedCount);
              return Padding(
                padding: const EdgeInsets.all(16),
                child: Center(child: Text('$done / $total')),
              );
            },
          ),
        ],
      ),
      body: HoneycombConsumer(
        builder: (context, ref, _) {
          final todos = ref.watch(todosState);

          return ListView.builder(
            itemCount: todos.length,
            itemBuilder: (context, index) {
              final todo = todos[index];
              return CheckboxListTile(
                value: todo.completed,
                title: Text(
                  todo.title,
                  style: TextStyle(
                    decoration:
                        todo.completed ? TextDecoration.lineThrough : null,
                  ),
                ),
                onChanged: (value) {
                  final container = HoneycombScope.readOf(context);
                  final newTodos =
                      todos.map((t) {
                        if (t.id == todo.id) {
                          return t.copyWith(completed: value);
                        }
                        return t;
                      }).toList();
                  container.write(todosState, newTodos);
                },
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _addTodo(context),
        child: const Icon(Icons.add),
      ),
    );
  }

  void _addTodo(BuildContext context) {
    showDialog(
      context: context,
      builder: (dialogContext) {
        final controller = TextEditingController();
        return AlertDialog(
          title: const Text('Add Todo'),
          content: TextField(
            controller: controller,
            autofocus: true,
            decoration: const InputDecoration(hintText: 'Todo title'),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(dialogContext),
              child: const Text('Cancel'),
            ),
            TextButton(
              onPressed: () {
                if (controller.text.isNotEmpty) {
                  final container = HoneycombScope.readOf(context);
                  final todos = container.read(todosState);
                  final newId = todos.isEmpty ? 1 : todos.last.id + 1;
                  container.write(todosState, [
                    ...todos,
                    Todo(newId, controller.text),
                  ]);
                }
                Navigator.pop(dialogContext);
              },
              child: const Text('Add'),
            ),
          ],
        );
      },
    );
  }
}

// ============================================================
// Demo 3: Async Data - AsyncComputed + AsyncValue
// ============================================================

final selectedUserId = StateRef(1);

final userDetail = Computed.async((watch) async {
  final userId = watch(selectedUserId);

  // 模拟网络请求
  await Future.delayed(const Duration(seconds: 1));

  // 模拟数据
  final users = {
    1: {'name': 'Alice', 'email': '[email protected]'},
    2: {'name': 'Bob', 'email': '[email protected]'},
    3: {'name': 'Charlie', 'email': '[email protected]'},
  };

  if (!users.containsKey(userId)) {
    throw Exception('User not found');
  }

  return users[userId]!;
});

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Async Demo')),
      body: Column(
        children: [
          // User selector
          HoneycombConsumer(
            builder: (context, ref, _) {
              final currentId = ref.watch(selectedUserId);
              return Padding(
                padding: const EdgeInsets.all(16),
                child: SegmentedButton<int>(
                  segments: const [
                    ButtonSegment(value: 1, label: Text('User 1')),
                    ButtonSegment(value: 2, label: Text('User 2')),
                    ButtonSegment(value: 3, label: Text('User 3')),
                    ButtonSegment(value: 99, label: Text('Invalid')),
                  ],
                  selected: {currentId},
                  onSelectionChanged: (selected) {
                    HoneycombScope.readOf(
                      context,
                    ).write(selectedUserId, selected.first);
                  },
                ),
              );
            },
          ),

          const Divider(),

          // User detail with AsyncValue
          Expanded(
            child: HoneycombConsumer(
              builder: (context, ref, _) {
                final asyncUser = ref.watch(userDetail);

                return asyncUser.when(
                  loading:
                      () => const Center(child: CircularProgressIndicator()),
                  data:
                      (user) => Center(
                        child: Card(
                          margin: const EdgeInsets.all(32),
                          child: Padding(
                            padding: const EdgeInsets.all(24),
                            child: Column(
                              mainAxisSize: MainAxisSize.min,
                              children: [
                                const Icon(Icons.person, size: 64),
                                const SizedBox(height: 16),
                                Text(
                                  user['name']!,
                                  style: const TextStyle(fontSize: 24),
                                ),
                                const SizedBox(height: 8),
                                Text(user['email']!),
                              ],
                            ),
                          ),
                        ),
                      ),
                  error:
                      (error, _) => Center(
                        child: Column(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            const Icon(
                              Icons.error,
                              size: 64,
                              color: Colors.red,
                            ),
                            const SizedBox(height: 16),
                            Text('Error: $error'),
                          ],
                        ),
                      ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

// ============================================================
// Demo 4: Effects - Toast / Navigation 事件
// ============================================================

final toastEffect = Effect<String>(
  strategy: EffectStrategy.drop,
  name: 'toast',
);

final navigationEffect = Effect<String>(
  strategy: EffectStrategy.bufferN,
  bufferSize: 5,
  name: 'navigation',
);

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Effect Demo')),
      body: HoneycombListener<String>(
        effect: toastEffect,
        onEvent: (ctx, message) {
          ScaffoldMessenger.of(
            ctx,
          ).showSnackBar(SnackBar(content: Text(message)));
        },
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text('Effects Demo', style: TextStyle(fontSize: 24)),
              const SizedBox(height: 32),

              ElevatedButton.icon(
                icon: const Icon(Icons.check),
                label: const Text('Show Success Toast'),
                onPressed: () {
                  context.emit(toastEffect, '✅ Operation successful!');
                },
              ),
              const SizedBox(height: 16),

              ElevatedButton.icon(
                icon: const Icon(Icons.warning),
                label: const Text('Show Warning Toast'),
                onPressed: () {
                  context.emit(toastEffect, '⚠️ Please be careful!');
                },
              ),
              const SizedBox(height: 16),

              ElevatedButton.icon(
                icon: const Icon(Icons.error),
                label: const Text('Show Error Toast'),
                onPressed: () {
                  context.emit(toastEffect, '❌ Something went wrong!');
                },
              ),

              const SizedBox(height: 48),
              const Text(
                'Effect with drop strategy:\nEvents are lost if no listener',
                textAlign: TextAlign.center,
                style: TextStyle(color: Colors.grey),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// ============================================================
// Demo 5: Scope Override - 局部状态覆盖
// ============================================================

final themeColorState = StateRef(Colors.blue);

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Scope Override Demo')),
      body: Column(
        children: [
          // 全局主题色
          Expanded(
            child: _ColoredSection(title: 'Global Scope', showButtons: true),
          ),

          const Divider(height: 1),

          // 局部 Override - 强制红色
          Expanded(
            child: HoneycombScope(
              overrides: [themeColorState.overrideWith(Colors.red)],
              child: _ColoredSection(
                title: 'Override: Red',
                showButtons: false,
              ),
            ),
          ),

          const Divider(height: 1),

          // 局部 Override - 强制绿色
          Expanded(
            child: HoneycombScope(
              overrides: [themeColorState.overrideWith(Colors.green)],
              child: _ColoredSection(
                title: 'Override: Green',
                showButtons: false,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _ColoredSection extends StatelessWidget {
  const _ColoredSection({required this.title, required this.showButtons});

  final String title;
  final bool showButtons;

  @override
  Widget build(BuildContext context) {
    return HoneycombConsumer(
      builder: (context, ref, _) {
        final color = ref.watch(themeColorState);

        return Container(
          color: color.withAlpha(51), // 0.2 * 255 ≈ 51
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(title, style: const TextStyle(fontSize: 20)),
                const SizedBox(height: 16),
                Container(
                  width: 60,
                  height: 60,
                  decoration: BoxDecoration(
                    color: color,
                    shape: BoxShape.circle,
                  ),
                ),
                if (showButtons) ...[
                  const SizedBox(height: 16),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      _colorButton(context, Colors.blue),
                      _colorButton(context, Colors.purple),
                      _colorButton(context, Colors.orange),
                    ],
                  ),
                ],
              ],
            ),
          ),
        );
      },
    );
  }

  Widget _colorButton(BuildContext context, Color color) {
    return Padding(
      padding: const EdgeInsets.all(4),
      child: IconButton(
        icon: Icon(Icons.circle, color: color),
        onPressed: () {
          HoneycombScope.readOf(context).write(themeColorState, color);
        },
      ),
    );
  }
}

// ============================================================
// Demo 6: Form Validation - SafeComputed 错误处理
// ============================================================

final emailState = StateRef('');
final passwordState = StateRef('');

// SafeComputed: 自动捕获异常
final emailValidation = SafeComputed<String>((watch) {
  final email = watch(emailState);
  if (email.isEmpty) throw FormatException('Email is required');
  if (!email.contains('@')) throw FormatException('Invalid email format');
  return email;
});

final passwordValidation = SafeComputed<String>((watch) {
  final password = watch(passwordState);
  if (password.isEmpty) {
    throw FormatException('Password is required');
  }
  if (password.length < 8) {
    throw FormatException('Password must be at least 8 characters');
  }
  return password;
});

// 表单整体是否有效
final isFormValid = Computed((watch) {
  final email = watch(emailValidation);
  final password = watch(passwordValidation);
  return email.isSuccess && password.isSuccess;
});

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Form Validation Demo')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            const Text('SafeComputed Demo', style: TextStyle(fontSize: 24)),
            const SizedBox(height: 8),
            const Text(
              'Exceptions are automatically caught and wrapped in Result.failure',
              style: TextStyle(color: Colors.grey),
            ),
            const SizedBox(height: 32),

            // Email field
            HoneycombConsumer(
              builder: (context, ref, _) {
                final validation = ref.watch(emailValidation);

                return TextField(
                  decoration: InputDecoration(
                    labelText: 'Email',
                    errorText: validation.when(
                      success: (_) => null,
                      failure:
                          (e, _) => e.toString().replaceFirst(
                            'FormatException: ',
                            '',
                          ),
                    ),
                    suffixIcon:
                        validation.isSuccess
                            ? const Icon(Icons.check, color: Colors.green)
                            : null,
                  ),
                  onChanged: (value) {
                    HoneycombScope.readOf(context).write(emailState, value);
                  },
                );
              },
            ),
            const SizedBox(height: 16),

            // Password field
            HoneycombConsumer(
              builder: (context, ref, _) {
                final validation = ref.watch(passwordValidation);

                return TextField(
                  obscureText: true,
                  decoration: InputDecoration(
                    labelText: 'Password',
                    errorText: validation.when(
                      success: (_) => null,
                      failure:
                          (e, _) => e.toString().replaceFirst(
                            'FormatException: ',
                            '',
                          ),
                    ),
                    suffixIcon:
                        validation.isSuccess
                            ? const Icon(Icons.check, color: Colors.green)
                            : null,
                  ),
                  onChanged: (value) {
                    HoneycombScope.readOf(context).write(passwordState, value);
                  },
                );
              },
            ),
            const SizedBox(height: 32),

            // Submit button
            HoneycombConsumer(
              builder: (context, ref, _) {
                final valid = ref.watch(isFormValid);

                return ElevatedButton(
                  onPressed:
                      valid
                          ? () {
                            ScaffoldMessenger.of(context).showSnackBar(
                              const SnackBar(
                                content: Text('✅ Form submitted!'),
                              ),
                            );
                          }
                          : null,
                  child: const Text('Submit'),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}
1
likes
150
points
151
downloads

Publisher

unverified uploader

Weekly Downloads

A simple, type-safe, codegen-free Flutter state management library with clear State/Effect separation, auto dependency tracking, and powerful Scope/Override mechanism.

Repository (GitHub)
View/report issues

Topics

#state-management #flutter #reactive #dependency-injection

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on aegis_honeycomb