A collection of flutter and dart libraries allowing you to consume complex external forms at runtime.

Overview

flutter_dynamic_forms

Build Status codecov

A collection of flutter and dart libraries providing a solution for Server Driven UI in your Flutter application.

Package Pub
expression_language pub package
dynamic_forms pub package
dynamic_forms_generator pub package
flutter_dynamic_forms pub package
flutter_dynamic_forms_components pub package

Main goal

The 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.

Simple example

Also see example project which contains working demo.

import 'package:flutter/material.dart';
import 'package:dynamic_forms/dynamic_forms.dart';
import 'package:flutter_dynamic_forms/flutter_dynamic_forms.dart';
import 'package:flutter_dynamic_forms_components/flutter_dynamic_forms_components.dart';

class SimpleForm extends StatelessWidget {
  final String xmlString;

  const SimpleForm({Key key, this.xmlString}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: ParsedFormProvider( 
          create: (_) => XmlFormManager(), // Or use JsonFormManager() to parse JSON data
          content: xmlString,
          parsers: getDefaultParserList(), // Optionally add your custom parsers
          child: FormRenderer<XmlFormManager>( // Use matching FormManager type registered above
            renderers: getReactiveRenderers(), // Optionally add your custom renderers
          ),
        ),
      ),
    );
  }
}
<?xml version="1.0" encoding="UTF-8"?>
<form id="form1">
    <textField
        id="firstName"
        label="Enter your first name">
    </textField>
    <textField
        id="lastName"
        label="Enter your last name">
        <textField.validations>
            <requiredValidation
                message="Last name is required"/>
        </textField.validations>
    </textField>
    <label
        id="fullNameLabel">
        <label.value>
            <expression>
                <![CDATA[
                    @firstName + (length(@firstName) > 0 && length(@lastName) > 0 ? " " : "") + @lastName
                ]]>
            </expression>
        </label.value>
    </label>
    <label>
        <label.value>
            <expression>
                <![CDATA[
                    "Welcome " + @fullNameLabel + "!"
                ]]>
            </expression>
        </label.value>
        <label.isVisible>
            <expression>
                <![CDATA[
                    !@hideWelcomeCheckBox && length(@fullNameLabel) > 0
                ]]>
            </expression>
        </label.isVisible>
    </label>
    <checkBox
        id="hideWelcomeCheckBox"
        value="false"
        label="Hide welcome message"/>
</form>

If you prefer JSON to describe your form please check this json example.

An animated gif showing example output
Example output

Simple Usage

Installation

Add following dependencies to your pubspec.yaml file:

flutter_dynamic_forms: <latest version>
flutter_dynamic_forms_components: <latest version>

Displaying the form

The 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:

First, you need to decide if you want to obtain your form data from XML or JSON. You can use either JsonFormManager or XmlFormManager. Those 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:

class CustomFormManager extends JsonFormManager { 
  Future<void> sendDataToServer() async {
    var properties = getFormProperties();
    // send properties to the server
  }
}

The 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<YourFormProvider>(context).

To render your Form on the screen, you can use FormRenderer widget:

  FormRenderer<XmlFormManager>( 
    renderers: getReactiveRenderers(),
    formManager: myFormManagerInstance, // this is optional, can be resolved from the FormProvider 
    dispatcher: _onFormElementEvent, // optional, when omitted, it will delegate all change events to from manager
  )

You can provide your FormManager as a type: FormRenderer<XmlFormManager>(...) and it will be automatically resolved from previously defined FormProvider or you can pass specific FormManager instance into the FormRenderer constructor.

This 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.

The 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:

  void _onFormElementEvent(FormElementEvent event) {
    if (event is ChangeValueEvent) {
      _formManager.changeValue(
          value: event.value,
          elementId: event.elementId,
          propertyName: event.propertyName,
          ignoreLastChange: event.ignoreLastChange);
    }
    // process your own events
  }

Collect data from the form

The 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.

To collect the data simply call:

List<FormPropertyValue> data = formManager.getFormData()

It 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. To submit the form you usually want to serialize this list and send it back to your server.

Writing a custom component

The flutter_dynamic_forms_components package contains only a set of basic components related to a simple form application. Because 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. Also 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.

To 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:

Parser

This 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.

import 'package:dynamic_forms/dynamic_forms.dart';
import 'check_box.dart';

class CheckBoxParser extends FormElementParser<CheckBox> {
  @override
  String get name => 'checkBox';

  @override
  FormElement getInstance() => CheckBox();

  @override
  void fillProperties(
    CheckBox checkBox,
    ParserNode parserNode,
    Element? parent,
    ElementParserFunction parser,
  ) {
    super.fillProperties(checkBox, parserNode, parent, parser);
    checkBox
      ..labelProperty = parserNode.getStringProperty('label')
      ..valueProperty = parserNode.getBoolProperty(
        'value',        
        isImmutable: false,
      );
  }
}

Model

Model 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. Every 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<T> 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.

import 'package:dynamic_forms/dynamic_forms.dart';

class CheckBox extends FormElement {
  static const String labelPropertyName = 'label';
  static const String valuePropertyName = 'value';

  Property<String> get labelProperty => properties[labelPropertyName];
  set labelProperty(Property<String> value) =>
      registerProperty(labelPropertyName, value);
  String get label =>
      labelProperty.value;
  Stream<String> get labelChanged => labelProperty.valueChanged;

  Property<bool> get valueProperty => properties[valuePropertyName];
  set valueProperty(Property<bool> value) =>
      registerProperty(valuePropertyName, value);
  bool get value =>
      valueProperty.value;
  Stream<bool> get valueChanged => valueProperty.valueChanged;

  @override
  FormElement getInstance() {
    return CheckBox();
  }
}

Renderer

This 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.

import 'package:flutter/material.dart';
import 'package:flutter_dynamic_forms/flutter_dynamic_forms.dart';

import 'check_box.dart';

class CheckBoxRenderer extends FormElementRenderer<CheckBox> {
  @override
  Widget render(
      CheckBox element,
      BuildContext context,
      FormElementEventDispatcherFunction dispatcher,
      FormElementRendererFunction renderer) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Row(
        children: <Widget>[
          StreamBuilder<bool>(
            initialData: element.value,
            stream: element.valueChanged,
            builder: (context, snapshot) {
              return Checkbox(
                onChanged: (value) => dispatcher(
                      ChangeValueEvent(
                        value: value,
                        elementId: element.id,
                      ),
                    ),
                value: snapshot.data,
              );
            },
          ),
          Padding(
            padding: EdgeInsets.only(left: 8),
            child: StreamBuilder<String>(
              initialData: element.label,
              stream: element.labelChanged,
              builder: (context, snapshot) {
                return Text(snapshot.data);
              },
            ),
          )
        ],
      ),
    );
  }
}

Generator

There 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 which lets you define components and their properties using simple YAML syntax.

Comments
  • Added Date Component

    Added Date Component

    added date component need to add intl package for formatting date. In xml looks like in below. <date id="formDate" label="Form Date" dateValue="" firstDate="2010" lastDate="2030" format="yyyy-dd-MM"></date>

    opened by kalismeras61 12
  • Can we create our own xml or json structure?

    Can we create our own xml or json structure?

    Hello, I have one project based on create dynamic form from xml/json. I saw your repo and it look awesome. But i don't have more detail in json object and my json structure is very simple like below. just need 3 type of element. Input's for strings,float and intergers, Date for dates and DropDown for List's.( Searcable dropdown)

    So can create my own structure ? or i must change my server response to your structure?

    { "data": [ { "type": "Checkbox", "label": "Cari Hesaplar", "value": 1, "list_fields": [ { "label": "Label 1", "value": 1 }, { "label": "Label 2", "value": 2 }, { "label": "Label 3", "value": 3 } ] }, { "type": "Input", "label": "Sözleşme No" }, { "type": "Date", "label": "Sözleşme Tarihi" }, { "type": "Date", "label": "Sözleşme Bitiş Tarihi" } ] }

    opened by kalismeras61 10
  • Validate form elements with numbers

    Validate form elements with numbers

    Hi, I have a textField for numbers, and need to perform simple validations on it. sort of like, @mytextField.value > 100

    The above fails because the value is a string and not a number.

    Are there any conversion functions to convert form values from string to int and double ? Or is there a better way to achieve the same validation ?

    TIA.

    opened by 4mitabh 8
  • JSON deserialization

    JSON deserialization

    Although I think XML serialized form is easier to read, we should also allow deserialization from some JSON format.

    We can even create separate packages, one for XML and one for JSON.

    opened by OndrejKunc 7
  • example run fail

    example run fail

    assets xml or json file has slider, but when run , error log: E/flutter (11857): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Exception: No parser for element name slider found E/flutter (11857): #0 FormParserService.parseElement (package:dynamic_forms/src/parser/form_parser_service.dart:28:7) E/flutter (11857): #1 JsonParserNode.getChildren. (package:dynamic_forms/src/parser/json/json_parser_node.dart:51:31) E/flutter (11857): #2 MappedListIterable.elementAt (dart:_internal/iterable.dart:417:29) E/flutter (11857): #3 ListIterator.moveNext (dart:_internal/iterable.dart:346:26) E/flutter (11857): #4 CastIterator.moveNext (dart:_internal/cast.dart:63:30) E/flutter (11857): #5 new List.from (dart:core-patch/array_patch.dart:35:29) E/flutter (11857): #6 Iterable.toList (dart:core/iterable.dart:400:5) E/flutter (11857): #7 JsonParserNode.getChildren (package:dynamic_forms/src/parser/json/json_parser_node.dart:53:14) E/flutter (11857): #8 FormGroupParser.parse (package:flutter_dynamic_forms_components/src/components/form_group/form_group_parser.g.dart:18:28) E/flutter (11857): #9 FormParserService.parseElement (package:dynamic_forms/src/parser/form_parser_service.dart:30:19) E/flutter (11857): #10 JsonParserNode.getChildren. (package:dynamic_forms/src/parser/json/json_parser_node.dart:51:31) E/flutter (11857): #11 MappedListIterable.elementAt (dart:_internal/iterable.dart:417:29) E/flutter (11857): #12 ListIterator.moveNext (dart:_internal/iterable.dart:346:26) E/flutter (11857): #13 CastIterator.moveNext (dart:_internal/cast.dart:63:30) E/flutter (11857): #14 new List.from (dart:core-patch/array_patch.dart:35:29) E/flutter (11857): #15 Iterable.toList (dart:core/iterable.dart:400:5) E/flutter (11857): #16 JsonParserNode.getChildren (package:dynamic_forms/src/parser/json/json_parser_node.dart:53:14) E/flutter (11857): #17 FormParser.parse (package:flutter_dynamic_forms_components/src/components/form/form_parser.g.dart:18:28) E/flutter (11857): #18 FormParserService.parseElement (package:dynamic_forms/src/parser/form_parser_service.dart:30:19) E/flutter (11857): #19 FormParserService.parse (package:dynamic_forms/src/parser/form_parser_service.dart:19:12) E/flutter (11857): #20 FormManagerBuilder.build (package:dynamic_forms/src/form_manager/form_manager_builder.dart:15:34) E/flutter (11857): #21 _SimpleFormScreenState._buildForm (package:example/simple_form/simple_form_screen.dart:52:39)

    opened by muziling 4
  • disable/enable validation

    disable/enable validation

    Disable/enable validation when element is not visible.

    The problem is in form.isFormValid, need a trigger that will disable/enable validation

    example json

    {
        "@name": "requiredValidation",
        "isVisible": {
            "expression": "@choice == \"test\""
        },
        "message": "Required!!!"
    }
    
    opened by Overman775 3
  • All validation except one instance is ignored if validation elements have no Id attribute

    All validation except one instance is ignored if validation elements have no Id attribute

    I've been trying out the example in the flutter_dynamic_forms_components and stumbled upon an issue relating to validation.

    The example forms in the assets folder have validation elements that do not specify id property attributes. The result of not having Ids results in the code here producing a Map with only one Validation element, whichever is the last to be yielded. This is because the key values were null values and you can't have duplicate keys in a Map.

    If I provide unique id values in the xml data then all the validations are included correctly. So I guess I'd suggest to enforce Validation elements have an Id or change the FormManager constructor to accept a list of Validations rather than a Map. What do you think @OndrejKunc ?

    Great library by the way.

    opened by markphillips100 3
  • The exclamation mark for equality operator (!=) is clashing with unary negate operator or postfix operator (!)

    The exclamation mark for equality operator (!=) is clashing with unary negate operator or postfix operator (!)

    I just ran a simple example "3 != 2", I got error for this. On changing the token for the unary negate operator and postfix operator to "!!" from "!", it worked.

    opened by raphire08 2
  • Running example failed at pub get stage

    Running example failed at pub get stage

    There seems to be a version conflict on the dependances.

    Below is that out put from flutter pub get

    Because no versions of flutter_bloc_extensions match >0.1.2 <0.2.0 and flutter_bloc_extensions 0.1.2 depends on flutter_bloc ^0.13.0, flutter_bloc_extensions ^0.1.2 requires flutter_bloc ^0.13.0. And because no versions of flutter_bloc match >0.13.0 <0.14.0, flutter_bloc_extensions ^0.1.2 requires flutter_bloc 0.13.0. And because flutter_bloc 0.13.0 depends on bloc ^0.13.0 and no versions of bloc match >0.13.0 <0.14.0, flutter_bloc_extensions ^0.1.2 requires bloc 0.13.0. And because bloc 0.13.0 depends on rxdart ^0.22.0 and every version of dynamic_forms from git depends on rxdart ^0.23.1, flutter_bloc_extensions ^0.1.2 is incompatible with dynamic_forms from git. And because every version of flutter_dynamic_forms_components from path depends on flutter_dynamic_forms from git which depends on dynamic_forms from git, flutter_bloc_extensions ^0.1.2 is incompatible with flutter_dynamic_forms_components from path. So, because example depends on both flutter_dynamic_forms_components from path and flutter_bloc_extensions ^0.1.2, version solving failed. Running "flutter pub get" in example... pub get failed (1; So, because example depends on both flutter_dynamic_forms_components from path and flutter_bloc_extensions ^0.1.2, version solving failed.)

    opened by siliconxp 2
  • Theming

    Theming

    Hi, how are people handling theming of the components?

    We have forked to add style properties using Theme, but wondering if there is a smarter way? If there isn't, does it make sense to create a pull request?

    opened by elliots 1
  • Expressions crach on id containe

    Expressions crach on id containe "_" or "."

    Please add warning like this:

    Warning! 
    Id tag cannon contain "_" or "."!
    

    i mean there

    <textField id="user.First_Name" label="Enter your first name" value="super_user">    
    

    this behavior crash expressions parser

    opened by mmmcorpsvit 1
  • Upgrade to petitparser 5

    Upgrade to petitparser 5

    There's a new major update of petitparser: 5.0.0. expression_language is unfortunately using version 4, making the update of the other popular packages impossible.

    opened by JFtechOfficial 1
  • hide form group

    hide form group

    Hi, Thanks for this very useful package. how to hide formgroup based on selection, like you did in the example. (hide welcome message label based on checkbox selection in the example)

    opened by sharpsaravanan 0
  • expressions.dart - support zero parameters.

    expressions.dart - support zero parameters.

    In order to implement a function such as LEN... which checks the length of a string.

    Please add the following check to isNumber under expressions.dart

    This allows zero an empty string to be evaluated as false.

    Ref: Line 334

    bool isNumber(String st) {
        if (st.isEmpty) {
          return false;
        }
    .....
    
    
    opened by philsnook 0
  • Saving filled data in fields in Bloc?

    Saving filled data in fields in Bloc?

    Hi, can someone please help me out with "caching" the field data? Basically im using the bloc example and I need to keep the fields as they were filled when users saves or exits the form, so when he clicks back into the form the Information they have put into those field will still be there. I am guessing its probably possible through FormManager.value function, but I am uncertain how to.

    opened by njonbayou 1
Releases(v1.0.0)
Owner
Ondřej Kunc
Ondřej Kunc
Quiver is a set of utility libraries for Dart that makes using many Dart libraries easier and more convenient, or adds additional functionality.

Quiver is a set of utility libraries for Dart that makes using many Dart libraries easier and more convenient, or adds additional functionality.

Google 905 Jan 2, 2023
This library contains methods that make it easy to consume Mpesa Api.

This library contains methods that make it easy to consume Mpesa Api. It's multi-platform, and supports CLI, server, mobile, desktop, and the browser.

Eddie Genius 3 Dec 15, 2021
Parser tool is a real-time compiler and runtime engine for strongly typed PEG parsers

parser_tool Version 0.1.1 (BETA) Parser tool is a real-time compiler and runtime engine for strongly typed PEG parsers. Parser tool contains libraries

null 6 Jun 28, 2021
A simple Flutter / Dart Utility class for converting complex objects to uri and query string

A simple Flutter / Dart Utility class for converting complex or nested objects to uri and query strings you can follow the the article on how this cla

Opata Joshua 5 Sep 7, 2022
Easily swap Flutter web renderers at runtime

renderer_switcher Swap Web Renderers in a Flutter Web app at runtime. Installation To use this plugin, add renderer_switcher as a dependency in your p

Wilson Wilson 12 Oct 21, 2022
A flutter application , that create dynamic forms from json data

checktyper A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if th

DotCoder 1 Aug 23, 2022
A flutter package provides controllers and editors for complex models and lists

This package provides controllers and editors for complex models and lists and is inspired by simplicity of TextEditingController. It encapsulates sta

null 2 Sep 1, 2022
Application that simplifies the search process in several supermarkets, allowing the user to define the shopping list in a few minutes.

economiz Application that solves the price research process in different supermarkets, facilitating and simplifying this process for the user. Getting

Guilherme Henrique 1 Dec 29, 2021
Contains utility functions and classes in the style of dart:collection to make working with collections easier

The collection package for Dart contains a number of separate libraries with utility functions and classes that makes working with collections easier.

Dart 273 Dec 27, 2022
Collection of object-oriented Dart primitives

Dartoos — Dart Object-Oriented Software This package is a collection of object-oriented Dart primitives that implement classic data structures, algori

Dartoos 6 Nov 6, 2022
A collection of pixel-perfect iOS-styled components and properties for Flutter, following the official guidelines.

A collection of pixel-perfect iOS-styled components and properties for Flutter, following the official guidelines.

null 30 Nov 10, 2022
Collection of should have StateNotifiers for combining with Riverpod/Provider

Collection of custom StateNotifiers. Riverpod / StateNotifierProvider or Provider / LocatorMixin

Mehmet Esen 2 Feb 27, 2022
Official Git of flutter code-push made by Chimera inc. If you want to get more info or seek for biz corporation, you can contact [email protected].

中文版 Chimera Flutter Code Push Chimera is a Dart compiler developed by ourselves, which generates interpretable and executable bytecode to implement co

Waytoon 21 Oct 6, 2022
Chuanying - what you see is what you get. 传影--所见即所得

传影--所见即所得 简介 从前 想要制作证件照,需要先把图片用QQ传到电脑,再用PS手动抠图; 看到一句喜欢的话,想要记到电脑里,需要一个字一个字用键盘打出来; 看到黑板上老师写的公式,想要记到Word里,需要用MathType一点点打出来; 但是有没有可能,只用手机拍照,就能搞定上面所有的事,一步

null 16 Apr 8, 2022
Library for help you make userbot or bot telegram and support tdlib telegram database and only support nodejs dart and google-apps-script

To-Do telegram client dart ✅️ support multi token ( bot / userbot ) ✅️ support bot and userbot ✅️ support telegram-bot-api local server ✅️ support tel

Azka Full Snack Developer:) 73 Jan 7, 2023
A dart package to help you parse and evaluate infix mathematical expressions into their prefix and postfix notations.

A dart package to help you parse and evaluate infix mathematical expressions into their prefix and postfix notations.

Miguel Manjarres 2 Jan 28, 2022
A Dart build script that downloads the Protobuf compiler and Dart plugin to streamline .proto to .dart compilation.

A Dart build script that downloads the Protobuf compiler and Dart plugin to streamline .proto to .dart compilation.

Julien Scholz 10 Oct 26, 2022
Flutter package to help you lazily load and display pages of items as the user scrolls down your screen.

Flutter package to help you lazily load and display pages of items as the user scrolls down your screen.

Edson Bueno 425 Dec 13, 2022