Eine Sammlung von Flutter- und Dart -Bibliotheken, die eine Lösung für die servergesteuerte Benutzeroberfläche in Ihrer Flutter -Anwendung bereitstellen.
| Paket | Pub |
|---|---|
| Expression_Language | |
| dynamic_forms | |
| Dynamic_Forms_Generator | |
| flutter_dynamic_forms | |
| flutter_dynamic_forms_components |
Die Idee hinter diesem Projekt besteht darin, Ihre Komponenten über XML oder JSON auf dem Server zu definieren und sie im Flutter -Client zu konsumieren, ohne die App neu einzubringen. Das Hauptaugenmerk liegt auf der Fähigkeit, benutzerdefinierte Komponenten und komplexe Beziehungen zwischen ihren Eigenschaften zu definieren. Sie können beispielsweise benutzerdefinierte Validierungsregeln definieren, die Sichtbarkeit basierend auf einer Bedingung usw. umschalten. Dies ist besonders nützlich, wenn Sie mit Formularen arbeiten, die einige Benutzereingaben sammeln, aber es kann verwendet werden, um einen Flutter -Widget -Baum anzuzeigen.
Siehe auch Beispielprojekt, das funktionierende Demo enthält.
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 >Wenn Sie es vorziehen, JSON zu beschreiben, um Ihr Formular zu beschreiben, überprüfen Sie bitte dieses JSON -Beispiel.

Fügen Sie Ihren folgenden Abhängigkeiten zu Ihrer Datei pubspec.yaml hinzu:
flutter_dynamic_forms : <latest version>
flutter_dynamic_forms_components : <latest version> Die Bibliothek flutter_dynamic_forms_components enthält eine Reihe von vordefinierten Komponenten wie Label , CheckBox , RadioButtonGroup usw., damit Ihre App mit den Komponenten funktioniert, die Sie für die Ausführung der folgenden Schritte benötigen:
Zunächst müssen Sie entscheiden, ob Sie Ihre Formulardaten von XML oder JSON erhalten möchten. Sie können entweder JsonFormManager oder XmlFormManager verwenden. Diese Klassen kümmern sich um die Parsen Ihrer Formulare. Sie haben auch eine Getter form , die die Objektdarstellung Ihres XML/JSON -Formulars in Dart ist. Sie können auch einen nützlichen Betrieb auf dem Formular ausführen, z. B. das Manipulieren des Status des Formulars, wenn etwas in der Benutzeroberfläche passiert, das Formular validiert oder alle Daten aus dem Formular sammelt, damit es an den Server zurückgesendet werden kann. Wenn Sie benutzerdefinierte Logik in den FormManager schreiben müssen, können Sie sie problemlos erweitern:
class CustomFormManager extends JsonFormManager {
Future < void > sendDataToServer () async {
var properties = getFormProperties ();
// send properties to the server
}
} Der einfachste Weg, Ihren FormManager zu initialisieren, ist über ParsedFormProvider Widget. Es wird Ihren XML/JSON -Inhalt, die Liste der Parsers, benötigt und die FormManager -Instanz erstellt und sich auch um die Parsen Ihrer Daten kümmert. ParsedFormProvider verwendet das Provider -Paket unter der Haube, sodass der FormManager in Ihrem Widget -Subtree verfügbar ist, indem er FormProvider.of<YourFormProvider>(context) aufgerufen wird.
Um Ihr Formular auf dem Bildschirm zu rendern, können Sie das FormRenderer -Widget verwenden:
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
) Sie können Ihren FormManager als Typ angeben: FormRenderer<XmlFormManager>(...) und er wird automatisch von zuvor definierten FormProvider aufgelöst oder Sie können eine bestimmte FormManager -Instanz in den FormRenderer -Konstruktor übergeben.
Dieses Widget enthält auch eine Liste von Renderern, die steuert, wie jedes Modell in das Flutter -Widget übersetzt wird. Im obigen Beispiel verwenden wir eine Reihe vordefinierter Renderer. Das Wort reaktiv bedeutet, dass jede Komponente die Änderungen in der Formmodelleigenschaft anhört und sich selbst aktualisiert.
Der letzte optionale Parameter ist ein dispatcher . Sie können Ereignisse, die von FormElementEvent in Ihren Render -Klassen erstellt wurden, abwickeln. Wenn dispatcher -Parameter nicht bereitgestellt wird, werden nur Ereignisse vom Typ ChangeValueEvent verarbeitet und direkt an die FormManager -Instanz delegiert, wodurch Änderungen der Eigenschaftswerte verursacht werden. Verwenden Sie Ihren eigenen dispatcher -Handler, wenn Sie benutzerdefinierte Ereignisse senden müssen (wie ein 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
}Die Idee hinter dem Prozess, Daten an den Server zurückzuschicken, ist, dass wir nicht das gesamte Formular zurücksenden sollten, sondern nur Werte, die vom Benutzer geändert werden.
Um die Daten zu sammeln, rufen Sie einfach an:
List < FormPropertyValue > data = formManager. getFormData ()Es enthält eine Liste aller Eigenschaften, die in einer Komponenten -Parser -Definition als veränderlich gekennzeichnet waren. In Standardkomponenten sind dies die Eigenschaften, von denen erwartet wird, dass sie von einem Benutzer geändert werden. Jedes Element enthält die ID des Quellelements, des Eigenschaftsnamens und des Eigenschaftswerts. Um das Formular einzureichen, möchten Sie diese Liste normalerweise serialisieren und an Ihren Server zurücksenden.
Das Paket flutter_dynamic_forms_components enthält nur eine Reihe grundlegender Komponenten, die sich auf eine einfache Formularanwendung beziehen. Aufgrund einiger Anforderungen an meine alte App entspricht nicht alle Komponenten direkt den Flattern -Widgets. Ich denke, das könnte sich in Zukunft ändern. Auch beim Entwerfen von Komponenten können Sie jederzeit zwischen Komponenten auf niedrigem Niveau wie Label oder hochrangiger Komponenten wie UserProfile wählen. Bei dieser komplexen hochrangigen Komponente lassen Sie die Client-Anwendung das Aussehen des endgültigen Widgets vollständig steuern. Aus diesen Gründen würde ich jeder Anwendung empfehlen, ihre benutzerdefinierte Komponenten zu schreiben, um die vollständige Kontrolle über jede Eigenschaft zu erhalten.
Um eine benutzerdefinierte Komponente zu implementieren, müssen Sie 3 Klassen angeben: Parser , Model und Renderer . Parser und Modelle müssen dann registriert werden, wenn Sie das Formular erstellen, wie Sie im obigen Code sehen können. Zeigen wir es im CheckBox Beispiel:
Diese Klasse steuert, wie die Komponente in eine entsprechende Modellklasse deserialisiert wird. Es funktioniert sowohl auf XML als auch auf JSON. ParserNode Parameter enthält eine Sammlung von Methoden, mit denen Sie Werte aus dem aktuellen XML/JSON -Knoten analysieren können. Verwenden Sie den Parameter ElementParserFunction parser des Parse -Verfahrens, um Kinderknoten rekursiv zu analysieren.
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 ,
);
}
} Das Modell ist die Hauptkomponentendefinition ohne Flatternabhängigkeit. Eine Komponente kann andere Komponenten erweitern, die alle Eigenschaften erben. Es kann auch Komponenten als seine Kinder enthalten. Jede Eigenschaft kann entweder einen einfachen Wert oder den Ausdruck enthalten, der auf den Wert bewertet wird. Um beide Fälle abzudecken, müssen alle Eigenschaften mithilfe Property<T> definiert werden. Eigenschaften werden in einer einzigen Karte namens properties gespeichert, sodass Sie den gesamten Komponentenbaum leicht durchqueren können. Es ist eine gute Idee, Getter und Setter auf dieser Karte zu erstellen, damit Sie problemlos auf Eigenschaftswerte zugreifen und festlegen können.
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 ();
}
} Diese Klasse nimmt einfach das Modell und gibt ein Flattern -Widget zurück. Sie können auch die Änderungen in den Eigenschaften abonnieren, damit Ihr Widget ordnungsgemäß aktualisiert wird, wenn etwas auf dem Modell passiert. Verwenden Sie zu diesem Zweck den in jeder Eigenschaft definierten Stream oder verwenden Sie die Eigenschaftskomponenten propertyChanged , die den Stream zurückgibt und den Wert nachwendet, wenn sich eine Eigenschaft ändert. So definieren Sie die Benutzeroberfläche der Standardkomponenten in flutter_dynamic_forms_components , definieren Sie einfach Ihre Renderer für die vorhandenen Modelle. Sie können sogar mehrere Renderer haben und eine andere Benutzeroberfläche auf einem anderen Bildschirm zeigen. Verwenden Sie den FormElementRendererFunction renderer -Parameter der Render -Methode, um Kinder rekursiv zu rendern.
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);
},
),
)
],
),
);
}
} Bei der Implementierung Parser und Model gibt es viel Kesselplatten. Da die meisten Apps wahrscheinlich viele benutzerdefinierte Komponenten erstellen müssen, gibt es auch ein Generatorpaket, mit dem Sie Komponenten und deren Eigenschaften mit einer einfachen YAML -Syntax definieren können.