{"id":24044630,"url":"https://github.com/dev-cetera/df_pod","last_synced_at":"2025-07-22T04:33:14.550Z","repository":{"id":251845245,"uuid":"838616166","full_name":"dev-cetera/df_pod","owner":"dev-cetera","description":"A package offering tools to manage app state using ValueListenable objects called Pods.","archived":false,"fork":false,"pushed_at":"2025-07-12T23:12:13.000Z","size":630,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-13T01:10:49.414Z","etag":null,"topics":["bloc","dart","flutter","library","package","reactive","state-management"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/df_pod","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/dev-cetera.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,"zenodo":null},"funding":{"github":["robmllze","dev-cetera"],"patreon":"RobertMollentze"}},"created_at":"2024-08-06T02:44:53.000Z","updated_at":"2025-07-12T23:12:16.000Z","dependencies_parsed_at":"2024-08-29T15:30:45.551Z","dependency_job_id":"c118cf33-9a2e-49e0-be96-f1bf931a9039","html_url":"https://github.com/dev-cetera/df_pod","commit_stats":null,"previous_names":["robmllze/df_pod","dev-cetera/df_pod"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/dev-cetera/df_pod","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-cetera%2Fdf_pod","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-cetera%2Fdf_pod/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-cetera%2Fdf_pod/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-cetera%2Fdf_pod/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dev-cetera","download_url":"https://codeload.github.com/dev-cetera/df_pod/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-cetera%2Fdf_pod/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266428077,"owners_count":23926926,"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","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"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":["bloc","dart","flutter","library","package","reactive","state-management"],"created_at":"2025-01-08T23:32:32.754Z","updated_at":"2025-07-22T04:33:14.540Z","avatar_url":"https://github.com/dev-cetera.png","language":"Dart","funding_links":["https://github.com/sponsors/robmllze","https://github.com/sponsors/dev-cetera","https://patreon.com/RobertMollentze","https://www.buymeacoffee.com/dev_cetera","https://www.patreon.com/c/RobertMollentze"],"categories":[],"sub_categories":[],"readme":"\u003ca href=\"https://www.buymeacoffee.com/dev_cetera\" target=\"_blank\"\u003e\u003cimg align=\"right\" src=\"https://cdn.buymeacoffee.com/buttons/default-orange.png\" height=\"48\"\u003e\u003c/a\u003e\n\u003ca href=\"https://discord.gg/gEQ8y2nfyX\" target=\"_blank\"\u003e\u003cimg align=\"right\" src=\"https://raw.githubusercontent.com/dev-cetera/.github/refs/heads/main/assets/icons/discord_icon/discord_icon.svg\" height=\"48\"\u003e\u003c/a\u003e\n\nDart \u0026 Flutter Packages by dev-cetera.com \u0026 contributors.\n\n[![sponsor](https://img.shields.io/badge/sponsor-grey?logo=github-sponsors)](https://github.com/sponsors/dev-cetera)\n[![patreon](https://img.shields.io/badge/patreon-grey?logo=patreon)](https://www.patreon.com/c/RobertMollentze)\n[![pub](https://img.shields.io/pub/v/df_pod.svg)](https://pub.dev/packages/df_pod)\n[![tag](https://img.shields.io/badge/tag-v0.18.11-purple?logo=github)](https://github.com/dev-cetera/df_pod/tree/v0.18.11)\n[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/dev-cetera/df_pod/main/LICENSE)\n\n---\n\n[![banner](https://github.com/dev-cetera/df_safer_dart/blob/v0.18.11/doc/assets/banner.png?raw=true)](https://github.com/dev-cetera)\n\n\u003c!-- BEGIN _README_CONTENT --\u003e\n\n## ℹ️ Features\n\n- **Monadic Safety**: Uses `Option` and `Result` from [df_safer_dart](https://pub.dev/packages/df_safer_dart) to explicitly handle loading, data, and error states, eliminating `null` checks.\n- **Composable \u0026 Declarative**: Create derived state by mapping or reducing pods, with automatic updates when dependencies change.\n- **Automatic Memory Management**: Weakly referenced listeners and automatic child pod disposal prevent memory leaks without manual cleanup.\n- **Familiar API**: Extends `ValueNotifier` with enhanced capabilities, integrating seamlessly with Flutter's reactive model.\n- **Specialized Builder Widgets**: Includes `PodBuilder`, `PodListBuilder`, and `PollingPodBuilder` for efficient UI updates.\n- **Performance Controls**: Fine-tune UI rebuilds with `debounceDuration` and `cacheDuration`.\n- **Persistent State**: Provides `SharedPod` helpers for easy state persistence with `SharedPreferences`.\n\n## ℹ️ Core Concepts\n\nThe core unit of state is a **Pod**. By convention, pod variables are prefixed with `p` (e.g., `pCounter`, `pUser`) for easy identification.\n\n- **`Pod\u003cT\u003e`**: A mutable `ValueListenable\u003cT\u003e` for root state, directly created and updated.\n- **`ChildPod\u003cT\u003e`**: A read-only pod derived by mapping or reducing parent pods, updating automatically with parent changes.\n- **`ReducerPod`**: A `ChildPod` that combines multiple parent pods, updating when any parent changes.\n- **`PodBuilder`**: A widget that listens to a `Pod` and rebuilds the UI on value changes.\n- **`PodListBuilder`**: A widget that listens to a list of pods, rebuilding when any pod changes.\n\n## ℹ️ Quickstart\n\n### 1. Define a Root Pod\n\nA `Pod` holds mutable state, from simple values to complex objects.\n\n```dart\nfinal pCounter = Pod\u003cint\u003e(0);\nfinal pItems = Pod\u003cList\u003cString\u003e\u003e(['Apple', 'Banana']);\n```\n\n### 2. Build UI with `PodBuilder`\n\n`PodBuilder` listens to a pod and rebuilds the UI, handling synchronous pods, `Future\u003cPod\u003e`, or error-throwing futures.\n\n```dart\nPodBuilder\u003cint\u003e(\n  pod: pCounter,\n  builder: (context, snapshot) {\n    final count = snapshot.value.unwrap().unwrap();\n    return Text('Count: $count');\n  },\n);\n```\n\nFor asynchronous operations, `PodBuilder` manages loading, success, and error states.\n\n```dart\nFuture\u003cPod\u003cString\u003e\u003e fetchUser() async {\n  await Future\u003cvoid\u003e.delayed(const Duration(seconds: 2));\n  if (Random().nextBool()) {\n    return Pod('Jane Doe');\n  } else {\n    throw Exception('Failed to load user.');\n  }\n}\n\nPodBuilder\u003cString\u003e(\n  pod: fetchUser(),\n  builder: (context, snapshot) {\n    final option = snapshot.value;\n    if (option.isNone()) {\n      return const CircularProgressIndicator();\n    }\n    final result = option.unwrap();\n    if (result.isErr()) {\n      final err = result.err().unwrap();\n      return Text('Error: ${err.error}');\n    }\n    final ok = result.unwrap();\n    return Text('Hello: $ok');\n  },\n),\n```\n\n### 3. Update a Pod's State\n\nModify a `Pod` using `.set()` or `.update()`. Listeners like `PodBuilder` or `ChildPod` react automatically.\n\n```dart\npCounter.update((currentCount) =\u003e currentCount + 1);\npItems.set(['Orange', 'Grape']);\n```\n\n### 4. Create Derived State with `.map()` and `.reduce()`\n\nCreate a read-only `ChildPod` by transforming a parent pod.\n\n```dart\nfinal pItems = Pod\u003cList\u003cString\u003e\u003e(['Apple', 'Banana', 'Cherry']);\nfinal pItemCount = pItems.map((itemList) =\u003e itemList.length);\nfinal pCountMessage = pItemCount.map((count) =\u003e 'You have $count items.');\n\nPodBuilder\u003cString\u003e(\n  pod: pCountMessage,\n  builder: (context, snapshot) =\u003e Text(snapshot.value.unwrap().unwrap()),\n);\n```\n\nUse `ReducerPod` to combine multiple pods.\n\n```dart\nfinal pFirstName = Pod('John');\nfinal pLastName = Pod('Doe');\nfinal pFullName = pFirstName.reduce(\n  pLastName,\n  (first, last) =\u003e '${first.getValue()} ${last.getValue()}',\n);\n\nPodBuilder\u003cString\u003e(\n  pod: pFullName,\n  builder: (context, snapshot) {\n    return Text('Full Name: ${snapshot.value.unwrap()}');\n  },\n);\n```\n\n## ℹ️ A Practical Example: Building a Search UI\n\n### 1. Define Root State\n\nStart with a root pod for the search query.\n\n```dart\nfinal pSearchQuery = Pod('');\n```\n\n### 2. Handle Async Operations and Errors\n\nUse `PodBuilder` to handle asynchronous API calls, managing loading, success, and error states.\n\n```dart\nPodBuilder\u003cList\u003cString\u003e\u003e(\n  pod: searchApi(query),\n  builder: (context, snapshot) {\n    if (snapshot.value.isNone()) {\n      return const CircularProgressIndicator();\n    }\n    final result = snapshot.value.unwrap();\n    if (result.isErr()) {\n      final error = result.err().unwrap().error;\n      return Text('Error: $error');\n    }\n    final products = result.unwrap();\n    return ListView(children: [for (final product in products) Text(product)]);\n  },\n);\n```\n\n### 3. Create Derived State with `.map()` and `.reduce()`\n\nBuild reactive state declaratively.\n\n```dart\nfinal pLatestResults = Pod\u003cList\u003cString\u003e\u003e([]);\nfinal pResultCount = pLatestResults.map((results) =\u003e results.length);\nfinal pSummaryMessage = pResultCount.reduce(\n  pSearchQuery,\n  (count, query) {\n    if (query.value.isEmpty) return 'Enter a search term.';\n    return 'Found ${count.value} result(s) for \"${query.value}\".';\n  },\n);\n```\n\n### 4. Build UI from Multiple Pods with `PodListBuilder`\n\n`PodListBuilder` efficiently handles multiple pods, rebuilding when any change.\n\n```dart\nPodListBuilder(\n  podList: [pResultCount, pSummaryMessage],\n  builder: (context, snapshot) {\n    final values = snapshot.value.map((e) =\u003e e.unwrap());\n    final [resultCount as int, summaryMessage as String] = values.toList();\n    return Card(child: Text(message));\n  },\n);\n```\n\nFor additional pods, such as cart or login status:\n\n```dart\nfinal pProductCount = Pod(10);\nfinal pCartTotal = Pod(99.99);\nfinal pIsLoggedIn = Pod(true);\n\nPodListBuilder(\n  podList: [pProductCount, pCartTotal, pIsLoggedIn],\n  builder: (context, snapshot) {\n    final values = snapshot.value.map((e) =\u003e e.unwrap()).toList();\n    final count = values[0] as int;\n    final total = values[1] as double;\n    final loggedIn = values[2] as bool;\n    if (!loggedIn) {\n      return const Text('Please log in.');\n    }\n    return Text('You have $count items in your cart. Total: \\$$total');\n  },\n);\n```\n\n### 5. Tune Performance with `debounceDuration` and `cacheDuration`\n\n- **debounceDuration**: Delays updates to prevent rapid API calls.\n- **cacheDuration**: Caches results for instant display, using a stable `Key`.\n\n```dart\nPodBuilder\u003cString\u003e(\n  pod: pSearchQuery,\n  // Even if pSearchQuery updates every millisecond, the builder will only update every 400ms.\n  debounceDuration: const Duration(milliseconds: 400),\n  builder: (context, querySnapshot) {\n    final query = querySnapshot.value.unwrap().unwrap();\n    if (query.isEmpty) return const Text('Enter a search term.');\n    return PodBuilder\u003cList\u003cString\u003e\u003e(\n      // A stable key is required for caching!\n      key: ValueKey(query),\n      pod: searchApi(query),\n       // Results are cached for 2 minutes of inactivity:\n      cacheDuration: const Duration(minutes: 2),\n      builder: (context, resultsSnapshot) {\n        // Handle loading/error/success\n      },\n    );\n  },\n);\n```\n\n## ℹ️ Advanced Features\n\n### Automatic Memory Management\n\n`df_pod` ensures safety and prevents leaks:\n\n- **Automatic Listener Cleanup**: Listeners use `WeakReferences`. When a `PodBuilder` is removed from the widget tree, its listener is garbage collected automatically.\n- **Automatic Child Disposal**: Disposing a parent pod disposes its derived children.\n\n```dart\nfinal pParent = Pod(0);\nfinal pChild = pParent.map((value) =\u003e value * 2);\nfinal pGrandChild = pChild.map((value) =\u003e value + 1);\npParent.dispose(); // Disposes pParent, pChild, and pGrandChild.\n```\n\n### The `addStrongRefListener` Method\n\nFor persistent listeners outside the UI, use `addStrongRefListener`, requiring manual removal.\n\n```dart\nvoid scope() {\n  // This is a strong referenced callback. It is tied to myListener. When myListener goes out of scope, pMyPod will tell the garbage collector it's ready to be disposed of.\n  final myListener = () =\u003e print('Pod changed!');\n  pMyPod.addStrongRefListener(strongRefListener: myListener);\n\n  // If you use anonymous functions or weak referenced functions, pMyPod will prematurely dispose these functions since they are not tied to a scope like myListener is.\n  pMyPod.addStrongRefListener(strongRefListener: () {\n     print('Anonymous weak referenced function!')\n  });\n  pMyPod.addStrongRefListener(strongRefListener: weakRefFunction);\n}\n\n// This is a weak reference.\nvoid weakRefFunction() {\n  print('Weak referenced functions should be avoided!')\n}\n```\n\n**Rule of Thumb**: Use `PodBuilder` for UI; reserve `addStrongRefListener` for non-UI logic.\n\n## ⚠️ Installation \u0026 Setup\n\n1. Use this package as a dependency by adding it to your `pubspec.yaml` file (see [here](https://pub.dev/packages/df_pod/install)).\n\n2. Update your `analysis_options.yaml` to the following. This is highly recommended because:\n\n- `prefer_function_declarations_over_variables: false`: The `addStrongRefListener` method requires a strong referenced variable function as an argument, so that it can be garbage collected automatically, and disabling this rule will prevent warnings.\n\n- `invalid_use_of_protected_member: error`: Certain methods in this package are protected to ensure they are only used within controlled contexts, preserving the integrity and consistency of the state management pattern. Enforcing this rule helps prevent misuse that could lead to unexpected behavior or security issues.\n\n- `invalid_override_of_non_virtual_member: error`: Non-virtual members are not designed to be overridden, as doing so could compromise the internal logic and functionality of the service. Enforcing this rule ensures that the core behavior of the package remains stable and predictable, preventing accidental or unauthorized changes.\n\n```yaml\ninclude: package:flutter_lints/flutter.yaml\n\nlinter:\n  rules:\n    prefer_function_declarations_over_variables: false\n\nanalyzer:\n  errors:\n    invalid_use_of_protected_member: error\n    invalid_override_of_non_virtual_member: error\n```\n\n\u003c!-- END _README_CONTENT --\u003e\n\n---\n\n☝️ Please refer to the [API reference](https://pub.dev/documentation/df_pod/) for more information.\n\n---\n\n## 💬 Contributing and Discussions\n\nThis is an open-source project, and we warmly welcome contributions from everyone, regardless of experience level. Whether you're a seasoned developer or just starting out, contributing to this project is a fantastic way to learn, share your knowledge, and make a meaningful impact on the community.\n\n### ☝️ Ways you can contribute\n\n- **Buy me a coffee:** If you'd like to support the project financially, consider [buying me a coffee](https://www.buymeacoffee.com/dev_cetera). Your support helps cover the costs of development and keeps the project growing.\n- **Find us on Discord:** Feel free to ask questions and engage with the community here: https://discord.gg/gEQ8y2nfyX.\n- **Share your ideas:** Every perspective matters, and your ideas can spark innovation.\n- **Help others:** Engage with other users by offering advice, solutions, or troubleshooting assistance.\n- **Report bugs:** Help us identify and fix issues to make the project more robust.\n- **Suggest improvements or new features:** Your ideas can help shape the future of the project.\n- **Help clarify documentation:** Good documentation is key to accessibility. You can make it easier for others to get started by improving or expanding our documentation.\n- **Write articles:** Share your knowledge by writing tutorials, guides, or blog posts about your experiences with the project. It's a great way to contribute and help others learn.\n\nNo matter how you choose to contribute, your involvement is greatly appreciated and valued!\n\n### ☕ We drink a lot of coffee...\n\nIf you're enjoying this package and find it valuable, consider showing your appreciation with a small donation. Every bit helps in supporting future development. You can donate here: https://www.buymeacoffee.com/dev_cetera\n\n\u003ca href=\"https://www.buymeacoffee.com/dev_cetera\" target=\"_blank\"\u003e\u003cimg src=\"https://cdn.buymeacoffee.com/buttons/default-orange.png\" height=\"40\"\u003e\u003c/a\u003e\n\n## 🧑‍⚖️ License\n\nThis project is released under the [MIT License](https://raw.githubusercontent.com/dev-cetera/df_pod/main/LICENSE). See [LICENSE](https://raw.githubusercontent.com/dev-cetera/df_pod/main/LICENSE) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdev-cetera%2Fdf_pod","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdev-cetera%2Fdf_pod","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdev-cetera%2Fdf_pod/lists"}