유연하고 런타임 구성이 가능한 폼을 구축하기 위한 기본적인 동적 폼 컴포넌트 세트를 제공하는 플러터 패키지입니다. JSON 또는 기타 데이터 구조를 사용하여 프로그래밍 방식으로 폼을 생성하고 렌더링할 수 있으며, 텍스트 필드, 체크박스, 드롭다운 등 다양한 입력 유형을 지원합니다. 적응성에 초점을 맞춰 플러터 위젯 시스템과 원활하게 통합되며, 안드로이드, iOS, 웹, 맥OS, 윈도우, 리눅스 등 여러 플랫폼을 지원합니다. 폼 레이아웃을 하드코딩하지 않고 사용자 입력을 커스터마이징해야 하는 애플리케이션에 이상적입니다.
사용 사례
사용자 등록 양식 구성 가능하게 만들기
동적 설문 또는 질문지 인터페이스 만들기
관리자 주도의 양식 수정 활성화
다국어 폼 콘텐츠 지원
변수 단계를 사용한 온보딩 흐름 구현
주요 기능
사전 정의된 동적 폼 위젯
JSON 기반 폼 구성
크로스 플랫폼 호환성
런타임 폼 렌더링
확장 가능한 컴포넌트 아키텍처
폼 검증 지원
적합한 대상
적응형 UI를 구축하는 개발자
동적 폼 로직이 필요한 앱
백엔드를 통해 폼 콘텐츠를 관리하는 팀
빠른 폼 프로토타이핑이 필요한 프로젝트
다양한 사용자 입력 요구 사항을 가진 애플리케이션
고려 사항
폼 정의를 위한 JSON 스키마 이해 필요
특수 입력을 위해 사용자 지정 구성 요소가 필요할 수 있습니다
매우 큰 양식에서의 성능 영향
기본 제공되는 내장 스타일 옵션이 제한적입니다
하위 동적 양식 엔진에 의존합니다
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
flutter_dynamic_forms
A collection of flutter and dart libraries providing a solution for Server Driven UI in your Flutter application.
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.
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:
classCustomFormManagerextendsJsonFormManager{
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 isChangeValueEvent) {
_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.
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.
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.
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.