{"id":18632911,"url":"https://github.com/esenmx/collection_notifiers","last_synced_at":"2026-05-05T21:31:43.248Z","repository":{"id":43048819,"uuid":"451225778","full_name":"esenmx/collection_notifiers","owner":"esenmx","description":"Collections with implementation of ValueListenable/ChangeNotifier for minimum rebuild and simplest syntax","archived":false,"fork":false,"pushed_at":"2025-12-05T16:09:43.000Z","size":276,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-09T03:06:37.369Z","etag":null,"topics":["collection","dart","data-structures","flutter","performance-optimization","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/esenmx.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"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}},"created_at":"2022-01-23T20:42:51.000Z","updated_at":"2025-12-05T16:09:46.000Z","dependencies_parsed_at":"2024-08-22T23:07:01.492Z","dependency_job_id":null,"html_url":"https://github.com/esenmx/collection_notifiers","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/esenmx/collection_notifiers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esenmx%2Fcollection_notifiers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esenmx%2Fcollection_notifiers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esenmx%2Fcollection_notifiers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esenmx%2Fcollection_notifiers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/esenmx","download_url":"https://codeload.github.com/esenmx/collection_notifiers/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esenmx%2Fcollection_notifiers/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32669380,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-05T11:29:49.557Z","status":"ssl_error","status_checked_at":"2026-05-05T11:29:48.587Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["collection","dart","data-structures","flutter","performance-optimization","state-management"],"created_at":"2024-11-07T05:13:40.841Z","updated_at":"2026-05-05T21:31:43.242Z","avatar_url":"https://github.com/esenmx.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# collection_notifiers\n\n[![Pub Version](https://img.shields.io/pub/v/collection_notifiers.svg)](https://pub.dev/packages/collection_notifiers)\n[![Build Status](https://github.com/esenmx/collection_notifiers/workflows/Build/badge.svg)](https://github.com/esenmx/collection_notifiers/actions)\n[![codecov](https://codecov.io/gh/esenmx/collection_notifiers/branch/master/graph/badge.svg)](https://codecov.io/gh/esenmx/collection_notifiers)\n[![License: MIT](https://img.shields.io/badge/license-MIT-purple.svg)](https://opensource.org/licenses/MIT)\n\n\u003e **Reactive collections for Flutter** — Lists, Sets, Maps, and Queues that automatically rebuild your UI when they change.\n\n---\n\n## ✨ Why collection_notifiers?\n\n| Without this package | With collection_notifiers |\n|---------------------|---------------------------|\n| Create copies on every change | Mutate in place |\n| Always triggers rebuilds | Only rebuilds when actually changed |\n| Verbose state management code | Clean, simple API |\n| Manual equality checks | Automatic optimization |\n\n```dart\n// ❌ Traditional approach - creates new objects, always rebuilds\nref.read(provider.notifier).update((state) =\u003e {...state, newItem});\n\n// ✅ With collection_notifiers - zero copies, smart rebuilds\nref.read(provider).add(newItem);\n```\n\n---\n\n## 📦 Installation\n\nAdd to your `pubspec.yaml`:\n\n```yaml\ndependencies:\n  collection_notifiers: ^2.0.0\n```\n\nThen run:\n\n```bash\nflutter pub get\n```\n\n---\n\n## 🚀 Quick Start\n\n### 1. Create a reactive collection\n\n```dart\nimport 'package:collection_notifiers/collection_notifiers.dart';\n\n// Just like regular collections, but reactive!\nfinal todos = ListNotifier\u003cString\u003e(['Buy milk', 'Walk dog']);\nfinal selectedIds = SetNotifier\u003cint\u003e();\nfinal settings = MapNotifier\u003cString, bool\u003e({'darkMode': false});\n```\n\n### 2. Use with ValueListenableBuilder\n\n```dart\nValueListenableBuilder\u003cList\u003cString\u003e\u003e(\n  valueListenable: todos,\n  builder: (context, items, child) {\n    return ListView.builder(\n      itemCount: items.length,\n      itemBuilder: (context, index) =\u003e Text(items[index]),\n    );\n  },\n)\n```\n\n### 3. Mutate and watch UI update automatically\n\n```dart\ntodos.add('Call mom');     // ✅ UI rebuilds\ntodos[0] = 'Buy eggs';     // ✅ UI rebuilds  \ntodos[0] = 'Buy eggs';     // ⏭️ No rebuild (same value!)\n```\n\n---\n\n## 📚 Available Notifiers\n\n| Type | Class | Best For |\n|------|-------|----------|\n| **List** | `ListNotifier\u003cE\u003e` | Ordered items, indices matter |\n| **Set** | `SetNotifier\u003cE\u003e` | Unique items, selections |\n| **Map** | `MapNotifier\u003cK,V\u003e` | Key-value data, settings |\n| **Queue** | `QueueNotifier\u003cE\u003e` | FIFO/LIFO operations |\n\n---\n\n## 🎯 Smart Notifications\n\nThe magic is in the optimization — methods only notify listeners when something **actually changes**:\n\n```dart\nfinal tags = SetNotifier\u003cString\u003e({'flutter', 'dart'});\n\ntags.add('rust');      // 🔔 Notifies — new element added\ntags.add('rust');      // 🔕 Silent — already exists\n\ntags.remove('rust');   // 🔔 Notifies — element removed  \ntags.remove('rust');   // 🔕 Silent — wasn't there\n\ntags.clear();          // 🔔 Notifies — set emptied\ntags.clear();          // 🔕 Silent — already empty\n```\n\nSame for Maps:\n\n```dart\nfinal config = MapNotifier\u003cString, int\u003e({'volume': 50});\n\nconfig['volume'] = 75;   // 🔔 Notifies — value changed\nconfig['volume'] = 75;   // 🔕 Silent — same value\nconfig['bass'] = 30;     // 🔔 Notifies — new key added\n```\n\n---\n\n## 💡 Common Patterns\n\n### Selection UI with SetNotifier\n\nPerfect for checkboxes, chips, and multi-select:\n\n```dart\nfinal selected = SetNotifier\u003cint\u003e();\n\n// In your widget\nCheckboxListTile(\n  value: selected.contains(itemId),\n  onChanged: (_) =\u003e selected.invert(itemId),  // Toggle with one call!\n  title: Text('Item $itemId'),\n)\n```\n\nThe `invert()` method toggles presence:\n\n- If item exists → removes it, returns `false`\n- If item missing → adds it, returns `true`\n\n### Settings with MapNotifier\n\n```dart\nfinal settings = MapNotifier\u003cString, dynamic\u003e({\n  'darkMode': false,\n  'fontSize': 14,\n  'notifications': true,\n});\n\n// Toggle dark mode\nsettings['darkMode'] = !settings['darkMode']!;\n\n// Only rebuilds if value actually changes\nsettings['fontSize'] = 14;  // No rebuild if already 14\n```\n\n### Todo List with ListNotifier\n\n```dart\nfinal todos = ListNotifier\u003cTodo\u003e();\n\n// Add\ntodos.add(Todo(title: 'Learn Flutter'));\n\n// Remove\ntodos.removeWhere((t) =\u003e t.completed);\n\n// Reorder\nfinal item = todos.removeAt(oldIndex);\ntodos.insert(newIndex, item);\n\n// Sort\ntodos.sort((a, b) =\u003e a.priority.compareTo(b.priority));\n```\n\n---\n\n## 🔌 State Management Integration\n\n### With Riverpod\n\n```dart\nfinal todosProvider = ChangeNotifierProvider((ref) {\n  return ListNotifier\u003cString\u003e(['Initial todo']);\n});\n\n// In widget\nclass TodoList extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final todos = ref.watch(todosProvider);\n    \n    return ListView.builder(\n      itemCount: todos.length,\n      itemBuilder: (context, i) =\u003e ListTile(\n        title: Text(todos[i]),\n        trailing: IconButton(\n          icon: Icon(Icons.delete),\n          onPressed: () =\u003e ref.read(todosProvider).removeAt(i),\n        ),\n      ),\n    );\n  }\n}\n```\n\n### With Provider\n\n```dart\nChangeNotifierProvider(\n  create: (_) =\u003e SetNotifier\u003cint\u003e(),\n  child: MyApp(),\n)\n\n// In widget\nfinal selected = context.watch\u003cSetNotifier\u003cint\u003e\u003e();\ncontext.read\u003cSetNotifier\u003cint\u003e\u003e().add(itemId);\n```\n\n### Vanilla Flutter (no packages)\n\n```dart\nclass MyWidget extends StatefulWidget {\n  @override\n  State\u003cMyWidget\u003e createState() =\u003e _MyWidgetState();\n}\n\nclass _MyWidgetState extends State\u003cMyWidget\u003e {\n  final _items = ListNotifier\u003cString\u003e();\n  \n  @override\n  void dispose() {\n    _items.dispose();  // Don't forget to dispose!\n    super.dispose();\n  }\n  \n  @override\n  Widget build(BuildContext context) {\n    return ValueListenableBuilder\u003cList\u003cString\u003e\u003e(\n      valueListenable: _items,\n      builder: (context, items, _) =\u003e /* your UI */,\n    );\n  }\n}\n```\n\n---\n\n## ⚠️ Important Notes\n\n### Element Equality\n\nSmart notifications rely on `==` comparison. For custom objects:\n\n```dart\n// ❌ Won't work - default object equality\nclass User {\n  final String name;\n  User(this.name);\n}\n\n// ✅ Works - proper equality\nclass User {\n  final String name;\n  User(this.name);\n  \n  @override\n  bool operator ==(Object other) =\u003e other is User \u0026\u0026 other.name == name;\n  \n  @override\n  int get hashCode =\u003e name.hashCode;\n}\n```\n\n**Pro tip:** Use [freezed](https://pub.dev/packages/freezed) or [equatable](https://pub.dev/packages/equatable) for automatic equality.\n\n### Always Dispose\n\nWhen using in StatefulWidgets, always dispose:\n\n```dart\n@override\nvoid dispose() {\n  myNotifier.dispose();\n  super.dispose();\n}\n```\n\n### Some Methods Always Notify\n\n`sort()` and `shuffle()` always notify because checking if order changed would be expensive.\n\n---\n\n## 📖 Migration from 2.x\n\n**Breaking change:** `SetNotifier.invert()` return value changed:\n\n- Now returns `true` if element was **added**\n- Now returns `false` if element was **removed**\n\n```dart\n// v2.x\nselected.invert(1);  // returned result of add() or remove()\n\n// v3.x  \nselected.invert(1);  // returns true if added, false if removed\n```\n\n---\n\nMade with 💙 for the Flutter community\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesenmx%2Fcollection_notifiers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fesenmx%2Fcollection_notifiers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesenmx%2Fcollection_notifiers/lists"}