{"id":19806661,"url":"https://github.com/timnew/response_builder","last_synced_at":"2025-07-16T04:42:25.785Z","repository":{"id":56838120,"uuid":"308570656","full_name":"timnew/response_builder","owner":"timnew","description":"A library helper Flutter App to consume all kind of data source, and provides some production-ready data source.","archived":false,"fork":false,"pushed_at":"2020-11-19T23:47:44.000Z","size":275,"stargazers_count":3,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-01T07:37:20.090Z","etag":null,"topics":["async","dart","flutter","future","request","state-management","stream","widget"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/response_builder","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/timnew.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}},"created_at":"2020-10-30T08:36:53.000Z","updated_at":"2022-09-24T13:31:21.000Z","dependencies_parsed_at":"2022-09-13T04:54:33.694Z","dependency_job_id":null,"html_url":"https://github.com/timnew/response_builder","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/timnew/response_builder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timnew%2Fresponse_builder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timnew%2Fresponse_builder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timnew%2Fresponse_builder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timnew%2Fresponse_builder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/timnew","download_url":"https://codeload.github.com/timnew/response_builder/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timnew%2Fresponse_builder/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265482230,"owners_count":23774017,"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":["async","dart","flutter","future","request","state-management","stream","widget"],"created_at":"2024-11-12T09:08:07.919Z","updated_at":"2025-07-16T04:42:25.731Z","avatar_url":"https://github.com/timnew.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# response_builder\n\n[![Star this Repo](https://img.shields.io/github/stars/timnew/response_builder.svg?style=flat-square)](https://github.com/timnew/response_builder)\n[![Pub Package](https://img.shields.io/pub/v/response_builder.svg?style=flat-square)](https://pub.dev/packages/response_builder)\n[![Build Status](https://img.shields.io/github/workflow/status/timnew/response_builder/Run-Test)](https://github.com/timnew/response_builder/actions?query=workflow%3ARun-Test)\n\n## Why create this library\n\n`FutureBuilder` `StreamBuilder` are the foundation to consume any data that loaded from network or persistent storage. `FutureBuilder`, `StreamBuilder` are designed to be robust, flexible but it is not designed to be convenient. In fact handling with `AsyncSnapshot` in the callback is actually kind of complicated and error-prone, especially when you needs busy state, initial value, or switching to observe new future/stream from the old ones. Any mistake in the callback could cause bug in UI.\n\nTo reduce the complexity and avoid repetitive work to implement `AsyncWidgetBuilder` a thousand times, this library introduce some mixins, which help developer to consume data from future, stream, value listenable or other kind of observable sources in a nice and easy way.\n\n## What response_builder can do\n\nIt enables flutter developer to implement following features with minimum effort:\n\n* **Loading Screen**: Show a loading view when data not comming back\n* **Error Screen**: Show an error screen when something goes wrong\n* **Empty Screen**: Some something useful when API returns an empty list (As `ListView` and others don't support to render empty collection)\n* **Refresh/Retry**: Fire the same request again when something goes wrong or just want to refresh the data\n* **Optimal update**: update the UI optimally first, and refresh UI again when API returns.\n\n## What's in library\n\n### Key types\n\n* Data Source\n  * [Request] - Listenable data source for 3-state asynchronous data\n  * [ResultListenable] and [ResultNotifier] - Listenable data source for 2-state synchronized data\n* Build Protocols\n  * [BuildAsyncSnapshot] - This protocol enable [BuildAsyncSnapshotActions] to consume 3-state data from [Future], [Stream] or [Request]\n  * [BuildResult] - This protocol enable enables [BuildResultListenable] to consume 2-state data from [ResultListenable]\n  * [BuildValue] - This protocol enable enables [BuildValueListenable] to consume value from [ValueListenable]\n  * [WithEmptyValue] - Protocol implement [BuildValue.buildValue] contract, which enables building actions to handle empty value\n* Build Actions\n  * [BuildAsyncSnapshotActions] - Actions run on [BuildAsyncSnapshot] protocol to consume 3-state `AsyncResult` data from [Future], [Stream] or [Request],\n  * [BuildResultListenable] - Actions run on [BuildResult] protocol, to consume 2-state `Result` from [ResultListenable]\n  * [BuildValueListenable] - Actions run on [BuildValue] protocol, to consume value from [ValueListenable]\n\n## Consume `AsyncResult` from `Future`\n\nTo consume `AsyncResult` is easy:\n\n1. Create widget, could be either stateless or stateful, them both works\n2. Implement `BuildAsyncResult` protocol by add `BuildAsyncSnapshot\u003cT\u003e` mixin to the widget class for `StatelessWidget` or to the `State` class for `StatefulWidget`. `T` is the the data type hold by `Future`.\n3. Implement a `Widget buildValue(BuildContext context, T value)` method, which is the contract to build widget when value is loaded from `Stream`, `Future`.\n4. Calling `buildFuture` in widget's `build` method.\n5. You're done.\n\n```dart\nclass MyWidget extends StatelessWidget with BuildAsyncSnapshot\u003cString\u003e {\n  final Future\u003cT\u003e dataSource;\n\n  MyWidget(this.dataSource);\n\n  @override\n  Widget build(BuildContext context) {\n      // Calling buildFuture is dataSource is Future\n      return buildFuture(dataSource);\n  }\n\n  @override\n  Widget buildValue(BuildContext context, String value) {\n    // Implement buildValue contract to render UI when value is successfully fetched\n    return Center(\n      child: Text(value),\n    );\n  }\n}\n```\n\nAs `Future` holds `AsyncResult`, which could be `Loading`, `Value` or `Error`, so `MyWidget` also has 3 different state, based on `AsyncResult`:\n\n* Renders a `CircularLoadingIndicator` in the middle of parent before result is ready.\n* Render the value in the middle of the screen, when `Future` yields value\n* Render `!` icon with message in `error color` of current theme in the middle of parent when `Future` yields error.\n\nNo more direct mess around the `AsyncSnapshot` or `FutureBuilder`.\n\n## Customize Error View / Loading View\n\nDefault loading view / error view is convenient when it is more the focus of future. But sometimes you might do want to have granular control on how they looks like.\nTo do so, you can\n\n* Override `buildError` method, which is the contract being used to build error view.\n* Override `buildLoading` method, which is the contract being used to build loading view.\n\nHere is some example, it builds UI in Cupertino style instead of Material style:\n\n```dart\nclass MyWidget extends StatelessWidget with BuildAsyncSnapshot\u003cList\u003cString\u003e\u003e {\n  final Future\u003cT\u003e dataSource;\n\n  MyWidget(this.dataSource);\n\n  @override\n  Widget build(BuildContext context) {\n      // Calling buildFuture is dataSource is Future\n      return buildFuture(dataSource);\n  }\n\n  @override\n  Widget buildLoading(BuildContext context) {\n    // Build Cupertino style UI instead of default Material style\n    return Center(\n      child: CupertinoActivityIndicator(),\n    );\n  }\n\n  @override\n  Widget buildError(BuildContext context, Object error) {\n    // Build Cupertino style UI instead of default Material style\n    final errorColor = CupertinoColors.systemRed;\n\n    return Center(\n      child: Row(children: [\n        Icon(CupertinoIcons.exclamationmark_circle, color: errorColor),\n        Text(error.toString(), style: TextStyle(color: errorColor)),\n      ]),\n    );\n  }\n\n  @override\n  Widget buildValue(BuildContext context, List\u003cString\u003e value) {\n    return ListView.builder(\n      itemCount: value.length,\n      builder: (context, index) =\u003e Text(value[index]),\n    );\n  }\n}\n```\n\n## Customize Error View / Loading View across the whole app\n\nOverriding `buildError` and `buildLoading` contract provides detailed control of how error view or loading view would like. But it is done on a case by case manner.\nSometimes, we want to change them across the whole app. This can be done by registering `DefaultLoadingBuilder` and `DefaultErrorBuilder`.\n\nFirstly, register the default builders somewhere convenient, such as in the main method\n\n```dart\nvoid main () {\n  DefaultBuildActions.registerDefaultLoadingBuilder((context) {\n    return Center(\n      child: CupertinoActivityIndicator(),\n    );\n  });\n\n  DefaultBuildActions.registerDefaultErrorBuilder((context, error) {\n    final errorColor = CupertinoColors.systemRed;\n\n    return Center(\n      child: Row(children: [\n        Icon(CupertinoIcons.xmark_circle, color: errorColor),\n        Text(error.toString(), style: TextStyle(color: errorColor)),\n      ]),\n    );\n  });\n\n  runApp(MyApp());\n}\n```\n\nThen remove the overrides of `buildError` and `buildLoading`, so it can use the default builders.\n\n```dart\nclass MyWidget extends StatelessWidget with BuildAsyncSnapshot\u003cList\u003cString\u003e\u003e {\n  final Future\u003cT\u003e dataSource;\n\n  MyWidget(this.dataSource);\n\n  @override\n  Widget build(BuildContext context) {\n      // Calling buildFuture is dataSource is Future\n      return buildFuture(dataSource);\n  }\n\n  @override\n  Widget buildValue(BuildContext context, List\u003cString\u003e value) {\n    return ListView.builder(\n      itemCount: value.length,\n      builder: (context, index) =\u003e Text(value[index]),\n    );\n  }\n}\n```\n\n## `WithEmptyValue` protocol\n\n`MyWidget` above might not work in every case, it would complain if the `Future` returns an `Empty Value`, in example, it is a `empty list`.\nAnd `ListView` can't build empty list, so it complains.\n\n`WithEmptyValue` is the protocol designed to address this particular issue:\n\n```dart\nclass MyWidget extends StatelessWidget with BuildAsyncSnapshot\u003cList\u003cString\u003e\u003e, WithEmptyValue\u003cList\u003cString\u003e\u003e  {\n  final Future\u003cT\u003e dataSource;\n\n  MyWidget(this.dataSource);\n\n  @override\n  Widget build(BuildContext context) {\n      // Calling buildFuture is dataSource is Future\n      return buildFuture(dataSource);\n  }\n\n  // Instead of implement buildValue, you should implement buildContent as minimal implementation\n  @override\n  Widget buildContent(BuildContext context, List\u003cString\u003e value) {\n    return ListView.builder(\n      itemCount: value.length,\n      builder: (context, index) =\u003e Text(value[index]),\n    );\n  }\n}\n```\n\nThere are 2 changes in this example from the one above:\n\n1. Add `WithEmptyValue\u003cList\u003cString\u003e\u003e ` to `MyWidget`.\n2. Instead of implement `buildValue` contract, it implements `buildContent` contract, with exactly same implementation.\n\nWith this code, when `Future` yield empty list, `MyWidget` just build an empty `Container` instead of `ListView`, so from user's perspective, UI renders \"nothing\".\n\n## Customize Empty Screen\n\nBy Default, `WithEmptyValue` renders an empty `Container` for empty value is received, but it can be customized by override `buildEmpty` contract:\n\n```dart\nclass MyWidget extends StatelessWidget with BuildAsyncSnapshot\u003cList\u003cString\u003e\u003e, WithEmptyValue\u003cList\u003cString\u003e\u003e  {\n  final Future\u003cT\u003e dataSource;\n\n  MyWidget(this.dataSource);\n\n  @override\n  Widget build(BuildContext context) {\n      // Calling buildFuture is dataSource is Future\n      return buildFuture(dataSource);\n  }\n\n  // Instead of implement buildValue, you should implement buildContent as minimal implementation\n  @override\n  Widget buildContent(BuildContext context, List\u003cString\u003e value) {\n    return ListView.builder(\n      itemCount: value.length,\n      builder: (context, index) =\u003e Text(value[index]),\n    );\n  }\n\n  @override\n  Widget buildEmpty(BuildContext context, List emptyContent) {\n    // Override Empty Screen\n    return Center(\n      child: Text(\"Hooray! No more remaining todo for today!\"),\n    );\n  }\n}\n```\n\n## Handle \"empty model\"\n\n`WithEmptyValue` understands common data types:\n\n* Anything is `Iterable`\n  * Most of the built-in collection types, such as  `List` `Set`  is covered.\n  * Majority of the 3rd party collection types should works too, such as the popular `BuiltList` or `KtList`.\n* Any kind of `Map`, which might be used to render form or data sheet or so.\n* `null` is always considered as `empty` by default\n\nTo deal with anything not covered by those 3 rules, an `UnsupportedError` would be thrown. In this case, `checkIsValueEmpty` contract need to be implemented manually to make it work.\n\nFor example, `MyTableView` build a table from a `Future\u003cList\u003cList\u003cString\u003e\u003e\u003e`, the row length is fixed to be `5`, but columns  not determined, which can be none.\nIn this case, the default `checkIsValueEmpty` logic won't work, as there are always `5` rows. Instead of checking the rows, we need to check columns.\n\n```dart\nclass MyTableView extends StatelessWidget with BuildAsyncSnapshot\u003cList\u003cList\u003cString\u003e\u003e\u003e, WithEmptyValue\u003cList\u003cList\u003cString\u003e\u003e\u003e {\n  final Future\u003cList\u003cList\u003cString\u003e\u003e\u003e future;\n\n  const MyTableView({Key key, this.future}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return buildFuture(future);\n  }\n\n  // Instead of implement buildValue, you should implement buildContent as minimal implementation\n  @override\n  Widget buildContent(BuildContext context, List\u003cList\u003cString\u003e\u003e content) {\n    return Table(\n      children: content.map((r) =\u003e _buildRow(r)).toList(growable: false),\n    )\n  }\n\n  Widget _buildRow(List\u003cString\u003e rowData) {\n    return TableRow(\n      children: rowData.map((c) =\u003e Text(c)).toList(growable: false),\n    );\n  }\n\n  @override\n  bool checkIsValueEmpty(List\u003cList\u003cString\u003e\u003e value) {\n    // Every row in table should have same number of columns, so check first row should be enough\n    return value.first.isEmpty;\n  }\n}\n```\n\n## \"Universal\" data builder\n\nThis library organised the code with a `protocol/contract/actions` based approach, so it actually enables the code to be flexible but not losing control. Here is a not very useful but interesting example to explain the idea:\n\nThis is is a universal widget that can consume a list of screen from:\n\n* `List\u003cString\u003e`:  static data, UI won't update once build\n* `ValueListenable`: observable sync data source, UI freshes when data source changed\n* `ResultListenable`: observable sync 2-state data source, error view would be shown if result is an error\n* `Future`: one-time observable async 3-state data source, a loading screen would be shown before result is ready, then a value view or error view would shown\n* `Stream`: on-going observable async 3-state data source, a loading screen would be shown before result is ready, UI would update if new result is sent by remote source\n* `Request`: on-going observable async 3-state data source, a loading screen would be shown before result is ready, UI would update by either controlled by remotely or locally.\n\n```dart\nclass UniversalDataList extends StatelessWidget with BuildAsyncSnapshot\u003cList\u003cString\u003e\u003e, WithEmptyValue\u003cList\u003cString\u003e\u003e  {\n  final dynamic dataSource;\n\n  MyWidget(this.dataSource);\n\n  @override\n  Widget build(BuildContext context) {\n      if(dataSource is ValueListenable\u003cList\u003cString\u003e\u003e) {\n        return buildValueListenable(dataSource); // action from `BuildValueListenable`, depends on BuildValue protocol\n      } else if(dataSource is ResultListenable\u003cList\u003cString\u003e\u003e) {\n        return buildResultListenable(dataSource); // action from `BuildResultListenable`, depends on BuildResult protocol\n      } else if(dataSource is Future\u003cList\u003cString\u003e\u003e) {\n        return buildFuture(dataSource); // action from `BuildAsyncSnapshotActions, depends on BuildAsyncSnapshot protocol\n      } else if(dataSource is Stream\u003cList\u003cString\u003e\u003e) {\n        return buildStream(dataSource); // action from `BuildAsyncSnapshotActions, depends on BuildAsyncSnapshot protocol\n      } else if(dataSource is Request\u003cList\u003cString\u003e\u003e) {\n        return buildRequest(dataSource); // action from `BuildAsyncSnapshotActions, depends on BuildAsyncSnapshot protocol\n      } else if(dataSource is List\u003cString\u003e) {\n        return buildValue(context, dataSource); // Calling buildValue contract from BuildValue protocol\n      } else {\n        throw UnsupportedError(\"Unsupported data source ${dataSource.runtimeType}\");\n      }\n  }\n\n  // Instead of implement buildValue, you should implement buildContent as minimal implementation\n  @override\n  Widget buildContent(BuildContext context, List\u003cString\u003e value) {\n    return ListView.builder(\n      itemCount: value.length,\n      builder: (context, index) =\u003e Text(value[index]),\n    );\n  }\n}\n```\n\n## What is `Request` and `ResultListenable`, and what they used for\n\nTo better explain wha `Request` and `ResultListable` is, some terminologies should be explained first\n\n### Some concepts\n\n* **Value** - The most common type of `data`, which is a `result` that holds a piece of information can be used to render UI\n* **Error** - A special type of `data` which indicates `result` is ready, something went wrong during the process. `Error` is exclusive to `value`.\n* **Loading** - A special type of `data` which indicates the `result` is not yet available. It is supposed to be temporary, which eventually replaced by either `value` or an `error`.\n* **Result** - A 2-state `data`, which could be either `value` or `error`. `Result` is synchronous `data`.\n* **AsyncResult** - A 3-state `data`, which can be `value`, `error`, or `loading`, `AsyncResult` is asynchronous `data`.\n* **Data** - The general concept of information used by app.\n* **Empty** - A special type of `Value`, which is a legal `value`, but contains no information, such as `[]`of `List\u003cString\u003e`.\n\n### Different type of data sources\n\nWith the concepts above, the data sources can be categories as:\n\n* `List\u003cString\u003e`:  The `data` itself, it is static, its value won't change.\n* `ValueListenable`: A synchronous observable data source, which provides `value`.\n* `ResultListenable`: A synchronous 2-state observable data source, which provides `result`.\n* `Future`: A one-time-use asynchronous 3-state observable data source, which provides `async result`\n  * `Future`  is `one-time-use` , so `loading` would only appears once, then its value fixed in either a `value` or `error`. Can't changed afterward.\n* `Stream`: An on-going asynchronous 3-state observable data source, which provides `async result`.\n  * `Stream` is `on-going` data source, whose value could changing across time as many times as it needs to be.\n  * `Stream`'s data is typically controlled by remote source, such as a file or remote server.\n  * `Stream`'s data can only be accessed by listener in asynchronous way.\n  * `Stream` needs to be listened before data is feeding, or previous values are lost.\n* `Request`: An on-going asynchronous 3-state observable data source that provides `async result`.\n  * `Request` is `on-going` data source, whose value could changing across time as many times as it needs to be.\n  * `Request`'s value can be accessible in a synchronous by any one who has its instance.\n  * `Request`'s value can be manipulated locally.\n  * `Request` can be listened as many times as it needs to be.\n  * `Request` can be listened by as many listeners simultaneously as it needs to be.\n  * `Request` can be fed with `future`, `async result` yielded by `future` goes into `request`.\n\n### Conclusion\n\nSo with this comparison, it is clear that:\n\n\u003e  `Request` is designed to be used as app state manager, while `future` or `stream` is more a low-level data source.\n\n\u003e `ResultListenable` is used to fill the gap in flutter framework, which is being lack of 2-state synchronous data source.\n\n## Loading data from network with `Request`\n\nUsing `Request` is much easier than explaining what `Request` is and what is is for.\n\nHere is an example that demonstrates loading some data from a search API:\n\n```dart\nclass MySearchRequest extends Request\u003cList\u003cSearchItem\u003e\u003e {\n  final String keywords;\n\n  MySearchRequest(this.keywords);\n\n  Future\u003cList\u003cSearchItem\u003e\u003e load() async {\n    final response = await searchApi.search(keywords: keywords);\n\n    if (response.statusCode != 200) {\n      throw NetworkException(\"Failed to execute search, please retry\");\n    }\n\n    return response.parseBody();\n  }\n}\n```\n\nEvery request should implement at least `load` method, which is the contract used to specify how the data should be loaded.\n\n## Use `Request` in widget\n\nBuild widget with `Request` can be easily done with `BuildAsyncSnapshot` protocol and `buildRequest` action:\n\n```dart\nclass SearchResultView extends StatelessWidget with BuildAsyncSnapshot\u003cList\u003cSearchItem\u003e\u003e, WithEmptyValue\u003cList\u003cSearchItem\u003e\u003e {\n  final MySearchRequest request;\n\n  SearchResultView(this.request);\n\n  @override\n  Widget build(BuildContext context) {\n    // use method from BuildAsyncSnapshot to render request\n    return buildRequest(request);\n  }\n\n  @override\n  Widget buildError(BuildContext context, Object error) {\n    return Center(\n      child: Row(children: [\n        Text(error.toString()),\n        TextButton(\n          child: Text(\"Retry\"),\n          onPressed: () =\u003e request.reload(), // Retry to do search again\n        )\n      ]),\n    );\n  }\n\n  @override\n  Widget buildContent(BuildContext context, List\u003cSearchItem\u003e content) {\n    return ListView.builder(\n      itemCount: content.length,\n      itemBuilder: (context, index) =\u003e SearchItemView(content[index]),\n    );\n  }\n}\n```\n\nBesides being used to async result data source, `Request` also provides APIs to do other common tasks, such as re-execute `load` and feed its result to request, aka `retry` when request holds an `error `or `refresh` when request holds a `value`.\n\n## Use `Request` as business logic controller instead of using `StatefulWidget`.\n\n`Request` provides a bunch of API to work with 3-state `async result` with ease, and every changes on its value would notifies UI, eventually have UI updated with contracts defined by `BuildAsyncResult`. So `Request` can be used a logical controller that manages app state, instead of using a `StatefulWidget`.\n\nUsing `Request` as business logic controller has a few benefits:\n\n* Decoupling the business logic from UI\n* Business logic can be tested independently without UI\n* UI can be stateless, which is cheapter to build and easier to maintain.\n\nAs `Request` is highly optimized for the scenario dealing with data loading, so in a few particular cases,`Request` could be a much-easier-to-use replacement of other complicated solutions, such as `bloc`, `mobx` or `redux` style `reducer`.\n\nAn example to make request do something more than just load:\n\n```dart\nclass MySearchRequest extends Request\u003cList\u003cSearchItem\u003e\u003e {\n  final String keywords;\n\n  MySearchRequest(this.keywords);\n\n  Future\u003cList\u003cSearchItem\u003e\u003e load() async {\n    final response = await searchApi.search(keywords: keywords);\n\n    if (response.statusCode != 200) {\n      throw NetworkException(\"Failed to execute search, please retry\");\n    }\n\n    return response.parseBody();\n  }\n\n  Future saveSearchResult(SearchResultFile file) async {\n    if (this.hasValue) { // Check whether request holds a value\n      // write search result to file if it exists\n      await file.writes(currentValue);\n    } else if (this.hasError) {\n      // writes error to file with writeError method\n      await file.writeError(currentError);\n    }\n    // Skip if request is loading.\n  }\n\n  Future loadSearchResult(SearchResultFile file) async {\n    Future\u003cList\u003cSearchItem\u003e\u003e Function() loadAction = file.read;\n\n    // Feed a future into request, it updates UI just as the UI is listening to the future.\n    await execute(loadAction);\n  }\n\n  void clearSearchResult() {\n    // update request's data with given value\n    putValue([]);\n  }\n\n  void trimResult(int limit) {\n    // Update request's value based on current value\n    // Exception happened during the updating is caught by request and rendered on UI automatically.\n    updateValue((current) =\u003e current.take(limit).toList());\n  }\n\n  Future appendFromFile(SearchResultFile file) {\n    // Update request's value based on current value in asynchronous way\n    // Exception happened during the updating is caught by request and rendered on UI automatically.\n    return updateValueAsync((current) async =\u003e current + await file.read());\n  }\n}\n```\n\n## handle 2-state synchronous result with `ResultNotifier`\n\n`ResultNotifier` is a parallel type to `ValueNotifier` but holds 2-state result instead of just value.\n\n2-state result is represented with `Result` from [async](https://pub.dev/packages/async) package\n\n`ResultNotifier` could be useful when dealing with the case that error in data is not fatal.\n\nHere is a simple example:\n\n`FormData` holds a list of string form-field values, which can be either valid or invalid.\n\n```dart\nclass FormData {\n  final Map\u003cString, ResultNotifier\u003cString\u003e\u003e fields;\n\n  FormData(Map\u003cString, String\u003e initialValues)\n      : fields = Map.fromIterables(\n          initialValues.keys, // field keys\n          initialValues.values.map(\n            // wrap initial with ResultNotifier\n            (initialValue) =\u003e ResultNotifier(initialValue),\n          ),\n        );\n\n  void invalidField(String fieldName) {\n    // String can be thrown\n    // Notifier would treat thrown string as error\n    // updateValue would only call its callback when it holds value\n    fields[fieldName].updateValue((current) =\u003e throw current);\n  }\n\n  void validField(String fieldName) {\n    // Fix error only execute when notifier holds error\n    // the returned value is used as value\n    // fixError would only call its callback when it holds error\n    fields[fieldName].fixError((error) =\u003e error);\n  }\n}\n```\n\n## Build widget with `ResultNotifier`/ `ResultListenable`\n\n`ResultListenable` can be consumed with `buildResultListenable` action on any type implemented `BuildResult` protocol:\n\n```dart\nclass FormFieldView extends StatelessWidget with BuildResult\u003cString\u003e {\n  final String fieldName;\n  final ResultListenable\u003cString\u003e listenable;\n\n  const FormFieldView({Key key, this.fieldName, this.listenable}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return Row(\n      mainAxisAlignment: MainAxisAlignment.spaceBetween,\n      children: [\n        Text(fieldName),\n        buildResultListenable(listenable),\n      ],\n    );\n  }\n\n  @override\n  Widget buildValue(BuildContext context, String value) {\n    return Text(value);\n  }\n\n  @override\n  Widget buildError(BuildContext context, Object error) {\n    final errorColor = Theme.of(context).errorColor;\n\n    final badData = error as String;\n\n    return Row(\n      mainAxisSize: MainAxisSize.min,\n      children: [\n        Padding(\n          padding: const EdgeInsets.symmetric(horizontal: 8.0),\n          child: Icon(Icons.error_outline, color: errorColor),\n        ),\n        Text(badData, style: TextStyle(color: errorColor)),\n      ],\n    );\n  }\n}\n```\n\n**HINT**  `WithEmptyValue`  can be used with`BuildResultListenable` to handle empty content too.\n\n## Consume `ValueListenable` with `BuildValueListenable`\n\nSimilar to `BuildResultListenable`, built-in `ValueListenable` can be consumed with `BuildValueListenable` with compatible manner.\n\n**HINT**  `WithEmptyValue`  can be used with`BuildValueListenable` to handle empty content too.\n\n## Implement Undo and Redo with `HistoryValueNotifier`\n\n`HistoryValueNotifier` is an implementation of `ValueListenable\u003cT\u003e` with `undo` and `redo` support built-in.\n\n```dart\n// Create a HistoryValueNotifier that remembers past 30 changes\nfinal userInputValue = HistoryValueNotifier\u003cString\u003e(31, initialValue: \"\");\n\n// Use userInputValue as normal `ValueListenable`\nbuildValueListener(userInput);\n\n// Undo last change\nuserInputValue.undo();\n\n// Redo last user change\nuserInputValue.redo();\n```\n\n## License\n\nThe MIT License (MIT)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimnew%2Fresponse_builder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftimnew%2Fresponse_builder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimnew%2Fresponse_builder/lists"}