{"id":13548709,"url":"https://github.com/tolo/result_notifier","last_synced_at":"2026-02-21T08:36:06.162Z","repository":{"id":218599290,"uuid":"746668988","full_name":"tolo/result_notifier","owner":"tolo","description":"Pragmatic and magic-free state management for Flutter","archived":false,"fork":false,"pushed_at":"2024-09-02T13:00:16.000Z","size":3797,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-23T03:47:13.545Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/result_notifier","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/tolo.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":"2024-01-22T13:29:08.000Z","updated_at":"2024-12-04T03:06:11.000Z","dependencies_parsed_at":"2024-02-07T14:40:25.849Z","dependency_job_id":"5133ad3d-3288-4ef2-be53-67799cfc966c","html_url":"https://github.com/tolo/result_notifier","commit_stats":null,"previous_names":["tolo/result_notifier"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/tolo/result_notifier","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolo%2Fresult_notifier","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolo%2Fresult_notifier/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolo%2Fresult_notifier/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolo%2Fresult_notifier/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tolo","download_url":"https://codeload.github.com/tolo/result_notifier/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolo%2Fresult_notifier/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29677588,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T06:23:40.028Z","status":"ssl_error","status_checked_at":"2026-02-21T06:23:39.222Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6: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":[],"created_at":"2024-08-01T12:01:13.504Z","updated_at":"2026-02-21T08:36:06.141Z","avatar_url":"https://github.com/tolo.png","language":"Dart","funding_links":[],"categories":["Dart"],"sub_categories":[],"readme":"# Result Notifier\n\n**Pragmatic quality-of-life enhancements to vanilla Flutter state management - simply [lagom](https://en.wikipedia.org/wiki/Lagom).**\n\n![result_notifier.png](https://raw.githubusercontent.com/tolo/result_notifier/main/doc/assets/result_notifier.jpg)\n\n**Result Notifier** is a simple and modest package for enhancing the state management that Flutter already provides out \nof the box. In other words, it's based on familiar and platform-native concepts, rather than introducing new \nabstractions and mental models. In fact, the package really is little more than a few additions to [ValueNotifier](https://api.flutter.dev/flutter/foundation/ValueNotifier-class.html) \nand [ChangeNotifier](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html) (ok, perhaps slightly more than a few, but nothing crazy). As the name of this package\nalludes to, one of the most important additions is the concept of a **Result** type, which can represent either some\n**Data**, an **Error** or a **Loading** state.\n\n[![style: lint](https://img.shields.io/badge/style-lint-4BC0F5.svg)](https://pub.dev/packages/lint)\n\n_**Note: v0.5.0 contains breaking changes - see [changelog](https://pub.dev/packages/result_notifier/changelog).**_\n\n\n## The essence of Result Notifier\n\nThere are basically four concepts that\nmake **[ResultNotifier](https://pub.dev/documentation/result_notifier/latest/result_notifier/ResultNotifier-class.html)**\ndifferent from `ValueNotifier` and `ChangeNotifier`:\n\n* It holds a **[Result](https://pub.dev/documentation/result_notifier/latest/result_notifier/Result-class.html)** value (an [algebraic data type](https://dart.dev/language/patterns#algebraic-data-types)) \n  and provides methods for accessing and mutating the value. The value (result) can be in one of three different states:\n    - **[Data](https://pub.dev/documentation/result_notifier/latest/result_notifier/Data-class.html)**: The result contains some concrete data.\n    - **[Error](https://pub.dev/documentation/result_notifier/latest/result_notifier/Error-class.html)**: Represents an error, along with the *previous* data, if any.\n    - **[Loading](https://pub.dev/documentation/result_notifier/latest/result_notifier/Loading-class.html)**: Represents a loading/reloading state, along with the *previous* data, if any.\n* It's **refreshable** - by providing a synchronous or asynchronous \"fetcher\" function, you can specify how the data should \n  be refreshed when needed (or stale).\n* It's **cacheable** - by setting a `cacheDuration`, you can specify how long the data should be cached before it's\n  considered stale.\n* It's **composable** - you can easily combine the data of multiple `ResultNotifier`s (and even other `ValueListenable`s)\n  using `CombineLatestNotifier` or for instance apply an effect using `EffectNotifier`. \n\n### Algebraic data types support in `Result`\nAs mentioned above, `Result` is an algebraic data type, which means you can use it in switch statements like this: \n\n```dart\nswitch (result) {\n  Data(data: var d, lastUpdate: var t) =\u003e Text(d),\n  Error(error: var e, stackTrace: var s, data: var d, lastUpdate: var t) =\u003e Text('Error: $e'),\n  Loading(data: var d, lastUpdate: var t) =\u003e const CircularProgressIndicator(), \n}\n```\n\n...or simply like this (whatever floats your boat): \n\n```dart\nswitch (result) {\n  (Data d) =\u003e Text(d.data),\n  (Error e) =\u003e Text('Error: ${e.error}'),\n  (_) =\u003e const CircularProgressIndicator()\n}\n```\n\u003cbr/\u003e\n\n## Getting Started\n\n1. Simply [add the dependency](https://pub.dev/packages/result_notifier/install) and start writing some notifiers!\n2. Dive into the starter [example](https://pub.dev/packages/result_notifier/example) (see more [here](https://github.com/tolo/result_notifier/blob/main/example/lib)) \n   \u003cbr/\u003e**Or** - just follow along below for a quick introduction to the basic concepts of Result Notifier. 👇\n\n\n### A simple start\n\nThe simplest form of notifier only holds a value, much like a `ValueNotifier`. But with `ResultNotifier`, the value is\nwrapped in a `Result` type, which can represent either some data, an error, or a loading state.\n\n```dart\nfinal notifier = ResultNotifier\u003cString\u003e(data: 'Hello...');\nprint(notifier.data); // Prints 'Hello...'\nprint(notifier.result); // Prints 'Data\u003cString\u003e(data: Hello..., lastUpdate: 2024-01-02 03:04:05.000006)'\n\nnotifier.toLoading(); // Convenience method to set the value to Loading, keeping the previous data.\n// Example use of some of the read-only properties for getting the current state of the notifier:\nnotifier.isLoading;\nnotifier.isData;\nnotifier.isError;\nnotifier.hasData;\n// If using cache `expiration`, you can also check if the data is fresh or stale:\nnotifier.isFresh;\nnotifier.isStale;\n\n// Set the a new data value (Data) using the actual data type directly, replacing the previous value/result:\nnotifier.data = 'Hello Flutter!';\n// Or, set the value to a new Result (in this case Data), replacing the previous value/result:\nnotifier.value = Data('Hello Flutter!');\n// or, set the value using a Future: \nnotifier.future = Future.value('Hello again Flutter!');\n// await notifier.future; // Optionally, wait for the future to complete.\n``` \n\u003cbr/\u003e\n\n### Fetching async data (e.g. from an API)\n\nOften you'll want to do something a little more elaborate, like fetching data from an API. In this case, you can use\n`FutureNotifier` (or ResultNotifier.future), which is a `ResultNotifier` that uses a \"fetcher\" function that returns a\nFuture.\n\n```dart\nfinal notifier = ResultNotifier\u003cString\u003e.future(\n  (_) async {\n    final response = await http.get(Uri.parse('https://www.boredapi.com/api/activity/'));\n    final json = jsonDecode(response.body) as Map\u003cString, dynamic\u003e;\n    return json['activity'] as String;\n  },\n  data: 'Test in production' // Optionally, set an initial value.\n  expiration: const Duration(seconds: 42), // Optionally, set a cache expiration.\n);\n\n// Refresh the data (i.e. call the fetcher function to udpate the data of the notifier):\nnotifier.refresh();\n``` \n\nRead more in the *Caching* section below. \n\n\u003cbr/\u003e\n\n## Observing (listening, watching...) changes\n\n### ValueListenableBuilder-based observation\n\nSince `ResultNotifier` implements `ValueListenable`, you can simply use `ValueListenableBuilder` to observe changes in\na Widget. However, this package also provides a method (`builder`) on \n[ResultBuilder](https://pub.dev/documentation/result_notifier/latest/result_notifier/ResultBuilder-class.html),\nwhich makes this a bit more convenient.\n\n```dart\nnotifier.builder((context, result, child) =\u003e switch (result) {\n  Data(data: var d) =\u003e Text(d),\n  Error(error: var e) =\u003e Text('Error: $e'),\n  Loading() =\u003e const CircularProgressIndicator(),\n}),\n``` \n\u003cbr/\u003e\n\n### \"Hooks-style\" inline observation\n\nIf you're into the \"hooks-style\" of building widgets, you can use the `watch` (available via extension methods on \n`ResultNotifier`, `ValueNotifier`, `ChangeNotifier` etc.) method to observe changes on `Listenable`s.  While \nsuperficially similar to `flutter_hooks`, this implementation doesn't suffer from some of the complexities of that \narchitectural style though, mainly because of a narrower scope and the fact that it relies on already existing \n`ResultNotifier` (or `Listenable`) instances.      \n\n```dart\nWatcher(builder: (context) {\n  final result = notifier.watch(context);\n  return Text(switch (result) {\n    Data(data: var d) =\u003e d,\n    Error(error: var e, data: var d) =\u003e 'Error - $d - $e',\n    Loading(data: var d) =\u003e 'Loading - $d',\n  });\n});\n```\n\u003cbr/\u003e\n\nThis type of observation is very lightweight, especially if using the stateless\n**[Watcher](https://pub.dev/documentation/result_notifier/latest/result_notifier/Watcher-class.html)** or\n**[WatcherWidget](https://pub.dev/documentation/result_notifier/latest/result_notifier/WatcherWidget-class.html)**\n(there is also a stateful implementation). Observation is facilitated through the use of the \n**[WatcherRef](https://pub.dev/documentation/result_notifier/latest/result_notifier/WatcherRef-class.html)** interface,\nwhich is implemented by the `BuildContext` \n(**[WatcherContext](https://pub.dev/documentation/result_notifier/latest/result_notifier/WatcherContext-class.html)**)\npassed to the `builder` in `Watcher` and `build` method in `WatcherWidget` etc. Although the `watch` method is provided \nby `WatcherRef`, it's often more convenient to use the extension method on `ResultNotifier` (and `Listenable` etc.).\n\nAlthough the use of `watch` is pretty straightforward, there are a few things to keep in mind: \n\n- The `watch` method can only be invoked within the `build` method of a `WatcherWidget`, or a Widget that mixes in \n  **[WatcherMixin](https://pub.dev/documentation/result_notifier/latest/result_notifier/WatcherMixin-mixin.html)** \n  or **[StatefulWatcherMixin](https://pub.dev/documentation/result_notifier/latest/result_notifier/StatefulWatcherMixin-mixin.html)**. \n- Disposal is handled automatically whenever you stop calling `watch` in the build method, or when the Widget is removed \n  from the tree (i.e. disposed).\n- Conditional logic in the build method works with `watch`, just remember the point above - i.e. the Widget will not be \n  rebuilt for `Listenable`s that weren't watched in the last call to `build`.   \n\nOne *additional* benefit of this style of observation is that it makes it easier to watch for changes in multiple \n`Listenable`s or `ResultNotifier`s, without resorting to a lot of nested `ValueListenableBuilder`s or `ResultBuilder`s. \nFor example:  \n\n```dart\nWatcher(builder: (context) {\n  final result1 = notifier1.watch(context);\n  final result2 = notifier2.watch(context);\n  final combined = [result1, result2].combine((data) =\u003e '${data[0]} and ${data[1]}');\n  return Text(switch (result) {\n    (Data\u003cString\u003e d) =\u003e d.data,\n    (Loading\u003cString\u003e l) =\u003e 'Loading - ${l.data}',\n    (Error\u003cString\u003e e) =\u003e 'Error - ${e.data} - ${e.error}',\n  });\n});\n``` \n\u003cbr/\u003e\n\n## Going deeper\n\n### Caching\n\nWhen using remote data, it's common to cache the data for some period of time and refresh it when it's stale. This\ncan be accomplished by setting the `cacheDuration` to an appropriate value. But remember that **caching is one of the\nroots of all evil**, so don't enable it unless you're sure you really need it, and only when you're finished\nimplementing the core functionality of your app. \n\n```dart\nfinal notifier = ResultNotifier\u003cString\u003e.future(\n  (_) async { ... },\n  data: 'Test in production' // Optionally, set an initial value.\n  expiration: const Duration(seconds: 42), // Optionally, set a cache expiration.\n);\n\n// If using cache `expiration`, you can also check if the data is fresh or stale:\nnotifier.isFresh; // Returns true in this example\nnotifier.isStale; // Returns false in this example\n\n// Refresh the data (i.e. call the fetcher function to udpate the data of the notifier):\nnotifier.refresh(); // In this example, the data will only be updated if refresh is called after 42 seconds has passed.\n``` \n\n### Effects\n\nYou can also use effects (see `EffectNotifier`), to build more complex chains of notifiers:\n\n```dart\nfinal notifier = ResultNotifier\u003cString\u003e(data: 'Þetta er frábært!');\nfinal effect = notifier.effect((_, input) =\u003e input.toUpperCase());\neffect.onData(print);\nnotifier.data = 'Þetta er frábært!'; // Prints: \"ÞETTA ER FRÁBÆRT!\"\n```\n\u003cbr/\u003e\n\nSee also the [effects](https://github.com/tolo/result_notifier/blob/main/example/lib/effects.dart) example for a more\ncomplete demonstration of these concepts. \n\n\n### Inline effects\nOne advantage of using the hooks-style approach to observing changes is that you can easily do things like combining the \nresults of multiple notifiers inline (without having to create a new notifier). This kind of effects is supported both \nthrough extension methods on `Iterable` as well on a set of `Record` definitions (see for instance\n**[ResultTuple](https://pub.dev/documentation/result_notifier/latest/result_notifier/ResultTuple.html)**). \n\n```dart\nWidget build(BuildContext context) {\n  final activity = activityRepository.watch();\n  final counter = experiencePoints.watch();\n\n  /// Here we combine the data from the two notifiers, using the `combine` (or `combineData`) extension method defined\n  /// in `ResultTuple` (there is also `ResultTriple` etc). \n  final resultFromRecord = (activity, counter).combine((a, b) =\u003e '$a - total experience points: $b');\n\n  /// You can also use similar functionality exposed as extension methods on Iterable. \n  final resultFromList = [activity, counter].combine((data) =\u003e '${data[0]} - count: ${data[1]}');\n\n  /// Or if you just want the data: \n  final resultData = (activity, counter).combineData((a, b) =\u003e '$a - count: $b');\n}\n``` \n\u003cbr/\u003e\n\n### ResultStore - a key-based store for notifiers  \nTo create an auto-disposable store of notifiers, each associated with a key - see [ResultStore](https://pub.dev/documentation/result_notifier/latest/result_notifier/ResultStore-class.html).\nResultStore can be useful to for instance support **pagination**, where each page is represented by a key and a unique \nnotifier. \n\n\n### ResourceProvider- Providing notifiers to a subtree of widgets\nA [ResourceProvider](https://pub.dev/documentation/result_notifier/latest/result_notifier/ResourceProvider-class.html)\ncan be used to handle the lifecycle (i.e. creation and disposal) of a notifier (or arbitrary resource), and provide it \nto a subtree of widgets. \n\n\n### Using regular `ValueNotifier`s / `ValueListenables`s\nThis package adds the extension method `toResultListenable` to `ValueListenable`, which transform it into a \n`ResultListenable` that can be used in for instance effects, such as `CombineLatestNotifier`.\n\n\n\n## Examples\n\nYou can find a more complete example [here](https://pub.dev/packages/result_notifier/example), and additional examples\nin the [examples directory](https://github.com/tolo/result_notifier/blob/main/example/lib) in the repository. \n\n\n### More examples\n\nFor an even more real-worldish example, check out [this fork](https://github.com/tolo/tmdb_movie_app) of Andrea\nBizzotto's TMDB Movie App, which uses Result Notifier instead of Riverpod. \n\n\n## When to use it - and when not to\n\nResult Notifier is probably most suitable for cases when your state management needs are in the ballpark of \"low to\nmoderate\", or as we say in Sweden: [lagom](https://en.wikipedia.org/wiki/Lagom). If you need more advanced state\nmanagement, you might want to reach for something more elaborate. But then again, maybe not - as in most cases, this\nvery much depends on your general application architecture and modularization. And remember - excessive use of state\nmanagement may also be a sign of a flawed architecture or over-engineering.\n\n\n## Things left to do...\n\nThe usual stuff, more tests and more docs 😅.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftolo%2Fresult_notifier","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftolo%2Fresult_notifier","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftolo%2Fresult_notifier/lists"}