{"id":15008494,"url":"https://github.com/ondrejkunc/flutter_dynamic_forms","last_synced_at":"2025-04-06T16:15:42.961Z","repository":{"id":50767321,"uuid":"191316227","full_name":"OndrejKunc/flutter_dynamic_forms","owner":"OndrejKunc","description":"A collection of flutter and dart libraries allowing you to consume complex external forms at runtime.","archived":false,"fork":false,"pushed_at":"2023-01-17T19:12:09.000Z","size":2229,"stargazers_count":204,"open_issues_count":27,"forks_count":59,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-04-06T16:15:36.425Z","etag":null,"topics":["components","dart","dartlang","dynamic-forms","flutter","flutter-package","json","xml"],"latest_commit_sha":null,"homepage":null,"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/OndrejKunc.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2019-06-11T07:20:49.000Z","updated_at":"2024-11-01T07:07:37.000Z","dependencies_parsed_at":"2023-02-10T11:30:15.049Z","dependency_job_id":null,"html_url":"https://github.com/OndrejKunc/flutter_dynamic_forms","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OndrejKunc%2Fflutter_dynamic_forms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OndrejKunc%2Fflutter_dynamic_forms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OndrejKunc%2Fflutter_dynamic_forms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OndrejKunc%2Fflutter_dynamic_forms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/OndrejKunc","download_url":"https://codeload.github.com/OndrejKunc/flutter_dynamic_forms/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247509238,"owners_count":20950232,"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":["components","dart","dartlang","dynamic-forms","flutter","flutter-package","json","xml"],"created_at":"2024-09-24T19:19:06.727Z","updated_at":"2025-04-06T16:15:42.938Z","avatar_url":"https://github.com/OndrejKunc.png","language":"Dart","readme":"# flutter_dynamic_forms\n\n[![Build Status](https://travis-ci.com/OndrejKunc/flutter_dynamic_forms.svg?branch=master)](https://travis-ci.com/OndrejKunc/flutter_dynamic_forms)\n[![codecov](https://codecov.io/gh/OndrejKunc/flutter_dynamic_forms/branch/master/graph/badge.svg)](https://codecov.io/gh/OndrejKunc/flutter_dynamic_forms)\n\nA collection of flutter and dart libraries providing a solution for Server Driven UI in your Flutter application. \n\n| Package                                                                                                                                       | Pub                                                                                                                                            |\n| --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |\n| [expression_language](https://github.com/OndrejKunc/flutter_dynamic_forms/tree/master/packages/expression_language)                           | [![pub package](https://img.shields.io/pub/v/expression_language.svg)](https://pub.dev/packages/expression_language)                           |\n| [dynamic_forms](https://github.com/OndrejKunc/flutter_dynamic_forms/tree/master/packages/dynamic_forms)                                       | [![pub package](https://img.shields.io/pub/v/dynamic_forms.svg)](https://pub.dev/packages/dynamic_forms)                                       |\n| [dynamic_forms_generator](https://github.com/OndrejKunc/flutter_dynamic_forms/tree/master/packages/dynamic_forms_generator)                   | [![pub package](https://img.shields.io/pub/v/dynamic_forms_generator.svg)](https://pub.dev/packages/dynamic_forms_generator)                   |\n| [flutter_dynamic_forms](https://github.com/OndrejKunc/flutter_dynamic_forms/tree/master/packages/flutter_dynamic_forms)                       | [![pub package](https://img.shields.io/pub/v/flutter_dynamic_forms.svg)](https://pub.dev/packages/flutter_dynamic_forms)                       |\n| [flutter_dynamic_forms_components](https://github.com/OndrejKunc/flutter_dynamic_forms/tree/master/packages/flutter_dynamic_forms_components) | [![pub package](https://img.shields.io/pub/v/flutter_dynamic_forms_components.svg)](https://pub.dev/packages/flutter_dynamic_forms_components) |\n\n## Main goal\n\nThe idea behind this project is to be able to define your components via XML or JSON on the server and consume it in the Flutter client without redeploying the app. The main focus is on the ability to define custom components and complex relationships between their properties. For example, you can define custom validation rules, toggle visibility based on a condition, etc. This makes it especially useful when working with forms collecting some user input but it can be used to display any flutter widget tree.\n\n## Simple example\n\nAlso see [example project](packages/flutter_dynamic_forms_components/example) which contains working demo.\n\n```dart\nimport 'package:flutter/material.dart';\nimport 'package:dynamic_forms/dynamic_forms.dart';\nimport 'package:flutter_dynamic_forms/flutter_dynamic_forms.dart';\nimport 'package:flutter_dynamic_forms_components/flutter_dynamic_forms_components.dart';\n\nclass SimpleForm extends StatelessWidget {\n  final String xmlString;\n\n  const SimpleForm({Key key, this.xmlString}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: SingleChildScrollView(\n        child: ParsedFormProvider( \n          create: (_) =\u003e XmlFormManager(), // Or use JsonFormManager() to parse JSON data\n          content: xmlString,\n          parsers: getDefaultParserList(), // Optionally add your custom parsers\n          child: FormRenderer\u003cXmlFormManager\u003e( // Use matching FormManager type registered above\n            renderers: getReactiveRenderers(), // Optionally add your custom renderers\n          ),\n        ),\n      ),\n    );\n  }\n}\n```\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cform id=\"form1\"\u003e\n    \u003ctextField\n        id=\"firstName\"\n        label=\"Enter your first name\"\u003e\n    \u003c/textField\u003e\n    \u003ctextField\n        id=\"lastName\"\n        label=\"Enter your last name\"\u003e\n        \u003ctextField.validations\u003e\n            \u003crequiredValidation\n                message=\"Last name is required\"/\u003e\n        \u003c/textField.validations\u003e\n    \u003c/textField\u003e\n    \u003clabel\n        id=\"fullNameLabel\"\u003e\n        \u003clabel.value\u003e\n            \u003cexpression\u003e\n                \u003c![CDATA[\n                    @firstName + (length(@firstName) \u003e 0 \u0026\u0026 length(@lastName) \u003e 0 ? \" \" : \"\") + @lastName\n                ]]\u003e\n            \u003c/expression\u003e\n        \u003c/label.value\u003e\n    \u003c/label\u003e\n    \u003clabel\u003e\n        \u003clabel.value\u003e\n            \u003cexpression\u003e\n                \u003c![CDATA[\n                    \"Welcome \" + @fullNameLabel + \"!\"\n                ]]\u003e\n            \u003c/expression\u003e\n        \u003c/label.value\u003e\n        \u003clabel.isVisible\u003e\n            \u003cexpression\u003e\n                \u003c![CDATA[\n                    !@hideWelcomeCheckBox \u0026\u0026 length(@fullNameLabel) \u003e 0\n                ]]\u003e\n            \u003c/expression\u003e\n        \u003c/label.isVisible\u003e\n    \u003c/label\u003e\n    \u003ccheckBox\n        id=\"hideWelcomeCheckBox\"\n        value=\"false\"\n        label=\"Hide welcome message\"/\u003e\n\u003c/form\u003e\n```\nIf you prefer JSON to describe your form please check this [json example](packages/flutter_dynamic_forms_components/example/assets/test_form1.json).\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"docs/simple_form.gif\" alt=\"An animated gif showing example output\" /\u003e\n  \u003cbr /\u003e\n  \u003cem\u003eExample output\u003c/em\u003e\n\u003c/div\u003e\n\n## Simple Usage\n\n### Installation\n\nAdd following dependencies to your `pubspec.yaml` file:\n\n```yaml\nflutter_dynamic_forms: \u003clatest version\u003e\nflutter_dynamic_forms_components: \u003clatest version\u003e\n```\n\n### Displaying the form\n\nThe `flutter_dynamic_forms_components` library contains set of predefined components like `Label`, `CheckBox`, `RadioButtonGroup` etc. To make your app work with those components you need to perform the following steps:\n\nFirst, you need to decide if you want to obtain your form data from XML or JSON. You can use either `JsonFormManager` or `XmlFormManager`.\nThose classes will take care of parsing your forms. They also have a getter `form` which is the object representation of your XML/JSON form in Dart. They can also perform some useful operation on the form, like manipulating the state of the form when something happens in the UI, validating the form, or collecting all the data from the form so it can be sent back to the server. If you need to write custom logic into the FormManager you can easily extend it:\n```dart\nclass CustomFormManager extends JsonFormManager { \n  Future\u003cvoid\u003e sendDataToServer() async {\n    var properties = getFormProperties();\n    // send properties to the server\n  }\n}\n```\n\nThe easiest way to initialize your `FormManager` is via `ParsedFormProvider` widget. It will take your XML/JSON content, list of parsers and it will create the `FormManager` instance and also takes care of parsing your data. `ParsedFormProvider` is using the `Provider` package under the hood, so the `FormManager` will be available in your widget subtree by calling `FormProvider.of\u003cYourFormProvider\u003e(context)`.\n\nTo render your Form on the screen, you can use `FormRenderer` widget:\n```dart\n  FormRenderer\u003cXmlFormManager\u003e( \n    renderers: getReactiveRenderers(),\n    formManager: myFormManagerInstance, // this is optional, can be resolved from the FormProvider \n    dispatcher: _onFormElementEvent, // optional, when omitted, it will delegate all change events to from manager\n  )\n```\n\nYou can provide your `FormManager` as a type: `FormRenderer\u003cXmlFormManager\u003e(...)` and it will be automatically resolved from previously defined `FormProvider` or you can pass specific `FormManager` instance into the `FormRenderer` constructor.\n\nThis widget also takes a list of renderers that controls how each model would be translated to the Flutter widget. In the example above, we use a set of predefined renderers. The word reactive means that each component will listen to the changes in the form model property and will update itself.\n\nThe last optional parameter is a `dispatcher`. It allows you to handle events derived from `FormElementEvent` produced in your render classes. When `dispatcher` parameter is not provided, only events of type `ChangeValueEvent` are processed and delegated directly to the FormManager instance causing changes of the property values. Use your own `dispatcher` handler if you need to send custom events (like a button click), but you should always let form manager handle the `ChangeValueEvent`:\n```dart\n  void _onFormElementEvent(FormElementEvent event) {\n    if (event is ChangeValueEvent) {\n      _formManager.changeValue(\n          value: event.value,\n          elementId: event.elementId,\n          propertyName: event.propertyName,\n          ignoreLastChange: event.ignoreLastChange);\n    }\n    // process your own events\n  }\n```\n\n### Collect data from the form\n\nThe idea behind the process of sending data back to the server is that we shouldn't send back the whole form but only values changed by the user.\n\nTo collect the data simply call:\n```dart\nList\u003cFormPropertyValue\u003e data = formManager.getFormData()\n```\n\nIt contains a list of all the properties which were marked as a mutable in a component parser definition. In default components, those are the properties that are expected to be changed by a user. Each item contains the id of the source element, property name, and property value.\nTo submit the form you usually want to serialize this list and send it back to your server.\n\n## Writing a custom component\n\nThe `flutter_dynamic_forms_components` package contains only a set of basic components related to a simple form application. \nBecause of some requirements on my old app, not all components naming directly corresponds to the flutter widgets. I think that may change in the future.\nAlso when designing components, you can always choose between low-level components like `Label` or high-level component like `UserProfile`. In the case of this complex high-level component you let the client application completely control the look of the final widget. For those reasons I would recommend for each application to write its custom set of components to have complete control over each property.\n\nTo implement a custom component you need to provide 3 classes: `Parser`, `Model`, and `Renderer`. Parsers and Models then need to be registered when you are building the form as you can see in the code above. Let's show it on the `CheckBox` example:\n\n### Parser\nThis class controls how the component would be deserialized into a corresponding model class. It works on both XML and JSON. `ParserNode` parameter contains a collection of methods that let you parse values from the current XML/JSON node. Use the `ElementParserFunction parser` parameter of the parse method to recursively parse children nodes.\n\n```dart\nimport 'package:dynamic_forms/dynamic_forms.dart';\nimport 'check_box.dart';\n\nclass CheckBoxParser extends FormElementParser\u003cCheckBox\u003e {\n  @override\n  String get name =\u003e 'checkBox';\n\n  @override\n  FormElement getInstance() =\u003e CheckBox();\n\n  @override\n  void fillProperties(\n    CheckBox checkBox,\n    ParserNode parserNode,\n    Element? parent,\n    ElementParserFunction parser,\n  ) {\n    super.fillProperties(checkBox, parserNode, parent, parser);\n    checkBox\n      ..labelProperty = parserNode.getStringProperty('label')\n      ..valueProperty = parserNode.getBoolProperty(\n        'value',        \n        isImmutable: false,\n      );\n  }\n}\n```\n\n### Model\nModel is the main component definition without any Flutter dependency. A component can extend other component inheriting all the properties. It can also contain components as its children.\nEvery property can contain either simple value or expression which is evaluated to the value. To be able to cover both of those cases all the properties must be defined using `Property\u003cT\u003e` syntax. Properties are stored in a single map called `properties` so you can easily traverse the whole component tree. It is a good idea to create getters and setters around this map so you can easily access and set property values.\n\n```dart\nimport 'package:dynamic_forms/dynamic_forms.dart';\n\nclass CheckBox extends FormElement {\n  static const String labelPropertyName = 'label';\n  static const String valuePropertyName = 'value';\n\n  Property\u003cString\u003e get labelProperty =\u003e properties[labelPropertyName];\n  set labelProperty(Property\u003cString\u003e value) =\u003e\n      registerProperty(labelPropertyName, value);\n  String get label =\u003e\n      labelProperty.value;\n  Stream\u003cString\u003e get labelChanged =\u003e labelProperty.valueChanged;\n\n  Property\u003cbool\u003e get valueProperty =\u003e properties[valuePropertyName];\n  set valueProperty(Property\u003cbool\u003e value) =\u003e\n      registerProperty(valuePropertyName, value);\n  bool get value =\u003e\n      valueProperty.value;\n  Stream\u003cbool\u003e get valueChanged =\u003e valueProperty.valueChanged;\n\n  @override\n  FormElement getInstance() {\n    return CheckBox();\n  }\n}\n```\n\n### Renderer\nThis class simply takes the model and returns a Flutter widget. You can also subscribe to the changes in the properties so your widget will be properly updated when something happens on the model. For this purpose use the Stream defined on each property or use the component property `propertyChanged` which returns Stream and emits value whenever any property changes. To redefine UI of the default components inside `flutter_dynamic_forms_components` simply define your renderers for the existing models. You can even have multiple renderers and show a different UI on a different screen. Use the `FormElementRendererFunction renderer` parameter of the render method to recursively render children.\n\n```dart\nimport 'package:flutter/material.dart';\nimport 'package:flutter_dynamic_forms/flutter_dynamic_forms.dart';\n\nimport 'check_box.dart';\n\nclass CheckBoxRenderer extends FormElementRenderer\u003cCheckBox\u003e {\n  @override\n  Widget render(\n      CheckBox element,\n      BuildContext context,\n      FormElementEventDispatcherFunction dispatcher,\n      FormElementRendererFunction renderer) {\n    return Padding(\n      padding: const EdgeInsets.all(8.0),\n      child: Row(\n        children: \u003cWidget\u003e[\n          StreamBuilder\u003cbool\u003e(\n            initialData: element.value,\n            stream: element.valueChanged,\n            builder: (context, snapshot) {\n              return Checkbox(\n                onChanged: (value) =\u003e dispatcher(\n                      ChangeValueEvent(\n                        value: value,\n                        elementId: element.id,\n                      ),\n                    ),\n                value: snapshot.data,\n              );\n            },\n          ),\n          Padding(\n            padding: EdgeInsets.only(left: 8),\n            child: StreamBuilder\u003cString\u003e(\n              initialData: element.label,\n              stream: element.labelChanged,\n              builder: (context, snapshot) {\n                return Text(snapshot.data);\n              },\n            ),\n          )\n        ],\n      ),\n    );\n  }\n}\n```\n\n### Generator\n\nThere is a lot of boilerplate when implementing `Parser` and `Model` classes. Because most of the apps will probably need to create a lot of custom components, there is also a [generator package](https://github.com/OndrejKunc/flutter_dynamic_forms/tree/master/packages/dynamic_forms_generator) which lets you define components and their properties using simple YAML syntax. ","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fondrejkunc%2Fflutter_dynamic_forms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fondrejkunc%2Fflutter_dynamic_forms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fondrejkunc%2Fflutter_dynamic_forms/lists"}