{"id":16401462,"url":"https://github.com/plugfox/control","last_synced_at":"2026-02-06T10:13:47.427Z","repository":{"id":214508025,"uuid":"736691994","full_name":"PlugFox/control","owner":"PlugFox","description":"Simple state management for Flutter with concurrency support.","archived":false,"fork":false,"pushed_at":"2024-10-16T13:58:44.000Z","size":359,"stargazers_count":18,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-19T04:19:26.435Z","etag":null,"topics":["architecture","concurrency","controller","dart","flutter","state","state-management"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PlugFox.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"patreon":"plugfox","custom":["https://www.buymeacoffee.com/plugfox","https://boosty.to/plugfox"]}},"created_at":"2023-12-28T15:45:54.000Z","updated_at":"2024-10-17T20:31:24.000Z","dependencies_parsed_at":"2023-12-28T17:00:22.109Z","dependency_job_id":"2c3f64d0-ca40-43a4-bc50-2bdeb186b694","html_url":"https://github.com/PlugFox/control","commit_stats":null,"previous_names":["plugfox/control"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PlugFox%2Fcontrol","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PlugFox%2Fcontrol/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PlugFox%2Fcontrol/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PlugFox%2Fcontrol/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PlugFox","download_url":"https://codeload.github.com/PlugFox/control/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221811380,"owners_count":16884305,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["architecture","concurrency","controller","dart","flutter","state","state-management"],"created_at":"2024-10-11T05:43:09.094Z","updated_at":"2026-02-06T10:13:47.421Z","avatar_url":"https://github.com/PlugFox.png","language":"Dart","readme":"# Control: State Management for Flutter\n\n[![Pub](https://img.shields.io/pub/v/control.svg)](https://pub.dev/packages/control)\n[![Actions Status](https://github.com/PlugFox/control/actions/workflows/checkout.yml/badge.svg)](https://github.com/PlugFox/control/actions)\n[![Coverage](https://codecov.io/gh/PlugFox/control/branch/master/graph/badge.svg)](https://codecov.io/gh/PlugFox/control)\n[![License: MIT](https://img.shields.io/badge/license-MIT-purple.svg)](https://opensource.org/licenses/MIT)\n[![Linter](https://img.shields.io/badge/style-linter-40c4ff.svg)](https://pub.dev/packages/linter)\n[![GitHub stars](https://img.shields.io/github/stars/plugfox/control?style=social)](https://github.com/plugfox/control/)\n\nA simple, flexible state management library for Flutter with built-in concurrency support.\n\n---\n\n## Features\n\n- 🎯 **Simple API** - Easy to learn and use\n- 🔄 **Flexible Concurrency** - Sequential, concurrent, or droppable operation handling\n- 🛡️ **Type Safe** - Full type safety with Dart's type system\n- 🔍 **Observable** - Built-in observer for debugging and logging\n- 🧪 **Well Tested** - Comprehensive test coverage\n- 📦 **Lightweight** - Minimal dependencies\n- 🔧 **Customizable** - Use Mutex for custom concurrency patterns\n\n## Installation\n\nAdd the following dependency to your `pubspec.yaml` file:\n\n```yaml\ndependencies:\n  control: ^1.0.0\n```\n\n## Quick Start\n\n### Basic Example\n\n```dart\n/// Counter state\ntypedef CounterState = ({int count, bool idle});\n\n/// Counter controller - concurrent by default\nclass CounterController extends StateController\u003cCounterState\u003e {\n  CounterController({CounterState? initialState})\n      : super(initialState: initialState ?? (idle: true, count: 0));\n\n  void increment() =\u003e handle(() async {\n        setState((idle: false, count: state.count));\n        await Future\u003cvoid\u003e.delayed(const Duration(milliseconds: 500));\n        setState((idle: true, count: state.count + 1));\n      });\n\n  void decrement() =\u003e handle(() async {\n        setState((idle: false, count: state.count));\n        await Future\u003cvoid\u003e.delayed(const Duration(milliseconds: 500));\n        setState((idle: true, count: state.count - 1));\n      });\n}\n```\n\n## Concurrency Strategies\n\n### 1. Concurrent (Default)\n\nOperations execute in parallel without waiting for each other:\n\n```dart\nclass MyController extends StateController\u003cMyState\u003e {\n  MyController() : super(initialState: MyState.initial());\n\n  // These operations run concurrently\n  void operation1() =\u003e handle(() async { ... });\n  void operation2() =\u003e handle(() async { ... });\n}\n```\n\n### 2. Sequential (with Mixin)\n\nOperations execute one after another in FIFO order:\n\n```dart\nclass MyController extends StateController\u003cMyState\u003e\n    with SequentialControllerHandler {\n  MyController() : super(initialState: MyState.initial());\n\n  // These operations run sequentially\n  void operation1() =\u003e handle(() async { ... });\n  void operation2() =\u003e handle(() async { ... });\n}\n```\n\n### 3. Droppable (with Mixin)\n\nNew operations are dropped if one is already running:\n\n```dart\nclass MyController extends StateController\u003cMyState\u003e\n    with DroppableControllerHandler {\n  MyController() : super(initialState: MyState.initial());\n\n  // If operation1 is running, operation2 is dropped\n  void operation1() =\u003e handle(() async { ... });\n  void operation2() =\u003e handle(() async { ... });\n}\n```\n\n### 4. Custom (with Mutex)\n\nUse `Mutex` directly for fine-grained control:\n\n```dart\nclass MyController extends StateController\u003cMyState\u003e {\n  MyController() : super(initialState: MyState.initial());\n\n  final _criticalMutex = Mutex();\n  final _batchMutex = Mutex();\n\n  // Sequential critical operations\n  void criticalOperation() =\u003e _criticalMutex.synchronize(\n    () =\u003e handle(() async { ... }),\n  );\n\n  // Sequential batch operations (different queue)\n  void batchOperation() =\u003e _batchMutex.synchronize(\n    () =\u003e handle(() async { ... }),\n  );\n\n  // Concurrent fast operations\n  void fastOperation() =\u003e handle(() async { ... });\n}\n```\n\n## Return Values from Operations\n\nThe `handle()` method is generic and can return values:\n\n```dart\nclass UserController extends StateController\u003cUserState\u003e {\n  UserController(this.api) : super(initialState: UserState.initial());\n\n  final UserApi api;\n\n  /// Fetch user and return the user object\n  Future\u003cUser\u003e fetchUser(String id) =\u003e handle\u003cUser\u003e(() async {\n    final user = await api.getUser(id);\n    setState(state.copyWith(user: user, loading: false));\n    return user; // Type-safe return value\n  });\n\n  /// Update user and return success status\n  Future\u003cbool\u003e updateUser(User user) =\u003e handle\u003cbool\u003e(() async {\n    try {\n      await api.updateUser(user);\n      setState(state.copyWith(user: user));\n      return true;\n    } catch (e) {\n      return false;\n    }\n  });\n}\n\n// Usage\nfinal user = await controller.fetchUser('123');\nprint('Fetched: ${user.name}');\n\nfinal success = await controller.updateUser(updatedUser);\nif (success) {\n  print('User updated successfully');\n}\n```\n\n**Note:** With `DroppableControllerHandler`, dropped operations return `null` instead of executing.\n\n## Usage in Flutter\n\n### Inject Controller\n\nUse `ControllerScope` to provide controller to widget tree:\n\n```dart\nclass App extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) =\u003e MaterialApp(\n    home: ControllerScope\u003cCounterController\u003e(\n      CounterController.new,\n      child: const CounterScreen(),\n    ),\n  );\n}\n```\n\n### Consume State\n\nUse `StateConsumer` to rebuild widgets when state changes:\n\n```dart\nclass CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) =\u003e Scaffold(\n    body: StateConsumer\u003cCounterController, CounterState\u003e(\n      builder: (context, state, _) =\u003e Text('Count: ${state.count}'),\n    ),\n    floatingActionButton: FloatingActionButton(\n      onPressed: () =\u003e context.controllerOf\u003cCounterController\u003e().increment(),\n      child: Icon(Icons.add),\n    ),\n  );\n}\n```\n\n### Use ValueListenable\n\nConvert state to `ValueListenable` for granular updates:\n\n```dart\nValueListenableBuilder\u003cbool\u003e(\n  valueListenable: controller.select((state) =\u003e state.idle),\n  builder: (context, isIdle, _) =\u003e ElevatedButton(\n    onPressed: isIdle ? () =\u003e controller.increment() : null,\n    child: Text('Increment'),\n  ),\n)\n```\n\n## Advanced Features\n\n### Error Handling\n\nThe `handle()` method provides built-in error handling:\n\n```dart\nvoid riskyOperation() =\u003e handle(\n  () async {\n    // Your operation\n    throw Exception('Something went wrong');\n  },\n  error: (error, stackTrace) async {\n    // Handle error\n    print('Error: $error');\n  },\n  done: () async {\n    // Always called, even if error occurs\n    print('Operation completed');\n  },\n  name: 'riskyOperation', // For debugging\n);\n```\n\n### Observer Pattern\n\nMonitor all controller events for debugging:\n\n```dart\nclass MyObserver implements IControllerObserver {\n  @override\n  void onCreate(Controller controller) {\n    print('Controller created: ${controller.name}');\n  }\n\n  @override\n  void onHandler(HandlerContext context) {\n    print('Handler started: ${context.name}');\n  }\n\n  @override\n  void onStateChanged\u003cS extends Object\u003e(\n    StateController\u003cS\u003e controller,\n    S prevState,\n    S nextState,\n  ) {\n    print('State changed: $prevState -\u003e $nextState');\n  }\n\n  @override\n  void onError(Controller controller, Object error, StackTrace stackTrace) {\n    print('Error in ${controller.name}: $error');\n  }\n\n  @override\n  void onDispose(Controller controller) {\n    print('Controller disposed: ${controller.name}');\n  }\n}\n\nvoid main() {\n  Controller.observer = MyObserver();\n  runApp(MyApp());\n}\n```\n\n### Mutex\n\nUse `Mutex` for custom synchronization:\n\n```dart\nfinal mutex = Mutex();\n\n// Method 1: synchronize (automatic unlock)\nawait mutex.synchronize(() async {\n  // Critical section\n});\n\n// Method 2: lock/unlock (manual control)\nfinal unlock = await mutex.lock();\ntry {\n  // Critical section\n  if (someCondition) {\n    unlock();\n    return; // Early exit\n  }\n  // More code\n} finally {\n  unlock();\n}\n\n// Check if locked\nif (mutex.locked) {\n  print('Mutex is currently locked');\n}\n```\n\n## Migration from 0.x to 1.0.0\n\nSee [MIGRATION.md](MIGRATION.md) for detailed migration guide.\n\n**Key changes:**\n- Remove `base` from controller classes\n- `ConcurrentControllerHandler` is deprecated (remove it)\n- Controllers are concurrent by default\n- Use `Mutex` for custom concurrency patterns\n\n## Best Practices\n\n1. **Choose the right concurrency strategy:**\n   - Default (concurrent) for independent operations\n   - Sequential for operations that must complete in order\n   - Droppable for operations that should cancel if busy\n   - Custom Mutex for complex scenarios\n\n2. **Use `handle()` for all async operations:**\n   - Automatic error catching\n   - Observer notifications\n   - Proper disposal handling\n\n3. **Keep state immutable:**\n   - Use records or immutable classes for state\n   - Always create new state instances\n\n4. **Dispose controllers:**\n   - Controllers are automatically disposed by `ControllerScope`\n   - Manual disposal only needed for manually created controllers\n\n## Advanced Usage\n\n### UI Feedback with Callbacks\n\nUse `error` and `done` callbacks to provide user feedback through SnackBars, dialogs, or notifications:\n\n```dart\nclass UserController extends StateController\u003cUserState\u003e {\n  UserController(this.api) : super(initialState: UserState.initial());\n\n  final UserApi api;\n\n  Future\u003cUser?\u003e updateProfile(\n    User user, {\n    void Function(User user)? onSuccess,\n    void Function(Object error)? onError,\n  }) =\u003e handle\u003cUser\u003e(\n    () async {\n      final updatedUser = await api.updateUser(user);\n      setState(state.copyWith(user: updatedUser));\n      onSuccess?.call(updatedUser);\n      return updatedUser;\n    },\n    error: (error, stackTrace) async {\n      onError?.call(error);\n    },\n    name: 'updateProfile',\n    meta: {'userId': user.id},\n  );\n}\n\n// Usage in UI\nElevatedButton(\n  onPressed: () =\u003e controller.updateProfile(\n    updatedUser,\n    onSuccess: (user) {\n      ScaffoldMessenger.of(context).showSnackBar(\n        SnackBar(content: Text('Profile updated: ${user.name}')),\n      );\n    },\n    onError: (error) {\n      ScaffoldMessenger.of(context).showSnackBar(\n        SnackBar(\n          content: Text('Error: $error'),\n          backgroundColor: Colors.red,\n        ),\n      );\n    },\n  ),\n  child: const Text('Update Profile'),\n)\n```\n\n### Interactive Dialogs During Processing\n\nAdd interactive dialogs in the middle of processing for user input:\n\n```dart\nclass AuthController extends StateController\u003cAuthState\u003e {\n  AuthController(this.api) : super(initialState: AuthState.initial());\n\n  final AuthApi api;\n\n  Future\u003cbool?\u003e login(\n    String email,\n    String password, {\n    required Future\u003cString\u003e Function() requestSmsCode,\n  }) =\u003e handle\u003cbool\u003e(\n    () async {\n      // Step 1: Initial login\n      final session = await api.login(email, password);\n\n      // Step 2: Check if 2FA is required\n      if (session.requires2FA) {\n        // Request SMS code from user via dialog\n        final smsCode = await requestSmsCode();\n\n        // Step 3: Verify SMS code\n        await api.verify2FA(session.id, smsCode);\n      }\n\n      setState(state.copyWith(isAuthenticated: true));\n      return true;\n    },\n    error: (error, stackTrace) async {\n      setState(state.copyWith(error: error.toString()));\n    },\n    name: 'login',\n    meta: {'email': email, 'requires2FA': true},\n  );\n}\n\n// Usage in UI\nElevatedButton(\n  onPressed: () =\u003e controller.login(\n    email,\n    password,\n    requestSmsCode: () async {\n      // Show dialog and wait for user input\n      final code = await showDialog\u003cString\u003e(\n        context: context,\n        builder: (context) =\u003e SmsCodeDialog(),\n      );\n      return code ?? '';\n    },\n  ),\n  child: const Text('Login'),\n)\n```\n\n### Debugging and Observability\n\nUse `name` and `meta` parameters for debugging, logging, and integration with error tracking services like Sentry or Crashlytics:\n\n```dart\nclass ControllerObserver implements IControllerObserver {\n  const ControllerObserver();\n\n  @override\n  void onHandler(HandlerContext context) {\n    // Log operation start with metadata\n    print('START | ${context.controller.name}.${context.name}');\n    print('META  | ${context.meta}');\n\n    final stopwatch = Stopwatch()..start();\n\n    context.done.whenComplete(() {\n      // Log operation completion with duration\n      stopwatch.stop();\n      print('DONE  | ${context.controller.name}.${context.name} | '\n            'duration: ${stopwatch.elapsed}');\n    });\n  }\n\n  @override\n  void onError(Controller controller, Object error, StackTrace stackTrace) {\n    final context = Controller.context;\n\n    if (context != null) {\n      // Send breadcrumbs to Sentry/Crashlytics\n      Sentry.addBreadcrumb(Breadcrumb(\n        message: '${controller.name}.${context.name}',\n        data: context.meta,\n        level: SentryLevel.error,\n      ));\n\n      // Report error with full context\n      Sentry.captureException(\n        error,\n        stackTrace: stackTrace,\n        hint: Hint.withMap({\n          'controller': controller.name,\n          'operation': context.name,\n          'metadata': context.meta,\n        }),\n      );\n    }\n  }\n\n  @override\n  void onStateChanged\u003cS extends Object\u003e(\n    StateController\u003cS\u003e controller,\n    S prevState,\n    S nextState,\n  ) {\n    final context = Controller.context;\n\n    // Log state changes with operation context\n    if (context != null) {\n      print('STATE | ${controller.name}.${context.name} | '\n            '$prevState -\u003e $nextState');\n      print('META  | ${context.meta}');\n    }\n  }\n\n  @override\n  void onCreate(Controller controller) {\n    print('CREATE | ${controller.name}');\n  }\n\n  @override\n  void onDispose(Controller controller) {\n    print('DISPOSE | ${controller.name}');\n  }\n}\n\n// Setup in main\nvoid main() {\n  Controller.observer = const ControllerObserver();\n  runApp(const App());\n}\n```\n\n**Benefits of using `name` and `meta`:**\n- **Debugging**: Easily track which operation is executing\n- **Logging**: Add context to logs for better traceability\n- **Profiling**: Measure operation duration and performance\n- **Error tracking**: Send rich context to Sentry/Crashlytics\n- **Analytics**: Track user actions with metadata\n- **Breadcrumbs**: Build execution trail for debugging crashes\n\n## Examples\n\nSee [example/](example/) directory for complete examples:\n- Basic counter\n- Advanced concurrency patterns\n- Error handling\n- Custom observers\n\n## Coverage\n\n[![](https://codecov.io/gh/PlugFox/control/branch/master/graphs/sunburst.svg)](https://codecov.io/gh/PlugFox/control/branch/master)\n\n## Changelog\n\nRefer to the [Changelog](https://github.com/PlugFox/control/blob/master/CHANGELOG.md) to get all release notes.\n\n## Maintainers\n\n- [Matiunin Mikhail aka Plague Fox](https://plugfox.dev)\n\n## Funding\n\nIf you want to support the development of our library, there are several ways you can do it:\n\n- [Buy me a coffee](https://www.buymeacoffee.com/plugfox)\n- [Support on Patreon](https://www.patreon.com/plugfox)\n- [Subscribe through Boosty](https://boosty.to/plugfox)\n\nWe appreciate any form of support, whether it's a financial donation or just a star on GitHub. It helps us to continue developing and improving our library. Thank you for your support!\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n","funding_links":["https://patreon.com/plugfox","https://www.buymeacoffee.com/plugfox","https://boosty.to/plugfox","https://www.patreon.com/plugfox"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplugfox%2Fcontrol","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplugfox%2Fcontrol","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplugfox%2Fcontrol/lists"}