Une collection de bibliothèques Flutter et Dart fournissant une solution pour l'interface utilisateur dirigée par le serveur dans votre application Flutter.
| Emballer | Pub |
|---|---|
| expression_language | |
| dynamic_forms | |
| dynamic_forms_generator | |
| flutter_dynamic_forms | |
| flutter_dynamic_forms_components |
L'idée derrière ce projet est de pouvoir définir vos composants via XML ou JSON sur le serveur et le consommer dans le client Flutter sans redéployer l'application. L'accent principal est la capacité de définir des composants personnalisés et des relations complexes entre leurs propriétés. Par exemple, vous pouvez définir des règles de validation personnalisées, basculer la visibilité en fonction d'une condition, etc.
Voir également l'exemple de projet qui contient une démo de travail.
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 >Si vous préférez JSON à décrire votre formulaire, veuillez consulter cet exemple JSON.

Ajoutez des dépendances suivantes à votre fichier pubspec.yaml :
flutter_dynamic_forms : <latest version>
flutter_dynamic_forms_components : <latest version> La bibliothèque flutter_dynamic_forms_components contient un ensemble de composants prédéfinis comme Label , CheckBox , RadioButtonGroup , etc. Pour que votre application fonctionne avec les composants dont vous avez besoin pour effectuer les étapes suivantes:
Tout d'abord, vous devez décider si vous souhaitez obtenir vos données de formulaire à partir de XML ou JSON. Vous pouvez utiliser JsonFormManager ou XmlFormManager . Ces cours s'occuperont de l'analyse de vos formulaires. Ils ont également une form Getter qui est la représentation d'objet de votre forme XML / JSON dans DART. Ils peuvent également effectuer une opération utile sur le formulaire, comme la manipulation de l'état du formulaire lorsque quelque chose se produit dans l'interface utilisateur, validant le formulaire ou collectant toutes les données du formulaire afin qu'il puisse être renvoyé au serveur. Si vous avez besoin d'écrire une logique personnalisée dans le FormManager, vous pouvez facilement l'étendre:
class CustomFormManager extends JsonFormManager {
Future < void > sendDataToServer () async {
var properties = getFormProperties ();
// send properties to the server
}
} Le moyen le plus simple d'initialiser votre FormManager est via un widget ParsedFormProvider . Il prendra votre contenu XML / JSON, la liste des analyseurs et créera l'instance FormManager et s'occupe également de l'analyse de vos données. ParsedFormProvider utilise le package Provider sous le capot, de sorte que le FormManager sera disponible dans votre sous-arbre de widget en appelant FormProvider.of<YourFormProvider>(context) .
Pour rendre votre formulaire à l'écran, vous pouvez utiliser le widget FormRenderer :
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
) Vous pouvez fournir votre FormManager en tant que type: FormRenderer<XmlFormManager>(...) et il sera automatiquement résolu à partir de FormProvider précédemment défini ou vous pouvez transmettre une instance FormManager spécifique dans le constructeur FormRenderer .
Ce widget prend également une liste de rendus qui contrôlent comment chaque modèle serait traduit dans le widget Flutter. Dans l'exemple ci-dessus, nous utilisons un ensemble de rendus prédéfinis. Le mot réactif signifie que chaque composant écoutera les modifications de la propriété du modèle de formulaire et se mettra à jour.
Le dernier paramètre facultatif est un dispatcher . Il vous permet de gérer des événements dérivés de FormElementEvent produit dans vos classes de rendu. Lorsque le paramètre dispatcher n'est pas fourni, seuls les événements de type ChangeValueEvent sont traités et délégués directement à l'instance FormManager provoquant des modifications des valeurs de propriété. Utilisez votre propre gestionnaire dispatcher si vous avez besoin d'envoyer des événements personnalisés (comme un clic à bouton), mais vous devez toujours laisser Form Manager gérer le 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
}L'idée derrière le processus de renvoyer des données au serveur est que nous ne devons pas renvoyer l'ensemble du formulaire mais que les valeurs modifiées par l'utilisateur.
Pour collecter les données, appelez simplement:
List < FormPropertyValue > data = formManager. getFormData ()Il contient une liste de toutes les propriétés qui ont été marquées comme mutables dans une définition d'analyse composante. Dans les composants par défaut, ce sont les propriétés qui devraient être modifiées par un utilisateur. Chaque élément contient l'ID de l'élément source, le nom de la propriété et la valeur de la propriété. Pour soumettre le formulaire, vous souhaitez généralement sérialiser cette liste et le renvoyer à votre serveur.
Le package flutter_dynamic_forms_components ne contient qu'un ensemble de composants de base liés à une application de formulaire simple. En raison de certaines exigences sur mon ancienne application, tous les composants qui nomment ne correspond directement aux widgets de flottement. Je pense que cela pourrait changer à l'avenir. De plus, lors de la conception de composants, vous pouvez toujours choisir entre des composants de bas niveau comme Label ou les composants de haut niveau comme UserProfile . Dans le cas de ce composant de haut niveau complexe, vous laissez l'application client contrôler complètement l'apparence du widget final. Pour ces raisons, je recommanderais à chaque application d'écrire son ensemble de composants personnalisés pour avoir un contrôle complet sur chaque propriété.
Pour implémenter un composant personnalisé, vous devez fournir 3 classes: Parser , Model et Renderer . Les analyseurs et les modèles doivent ensuite être enregistrés lorsque vous construisez le formulaire comme vous pouvez le voir dans le code ci-dessus. Affichons-le sur l'exemple CheckBox :
Cette classe contrôle comment le composant serait désérialisé en une classe de modèle correspondante. Il fonctionne à la fois sur XML et JSON. Le paramètre ParserNode contient une collection de méthodes qui vous permettent d'analyser les valeurs du nœud XML / JSON actuel. Utilisez le paramètre ElementParserFunction parser de la méthode Parse pour analyser récursivement les nœuds pour enfants.
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 ,
);
}
} Le modèle est la définition principale des composants sans aucune dépendance de flottement. Un composant peut étendre un autre composant héritant de toutes les propriétés. Il peut également contenir des composants comme ses enfants. Chaque propriété peut contenir une valeur ou une expression simple qui est évaluée à la valeur. Pour pouvoir couvrir ces deux cas, toutes les propriétés doivent être définies à l'aide de la syntaxe Property<T> . Les propriétés sont stockées sur une seule carte appelée properties afin que vous puissiez facilement traverser l'ensemble de l'arborescence des composants. C'est une bonne idée de créer des getters et des setteurs autour de cette carte afin que vous puissiez facilement accéder et définir les valeurs de propriété.
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 ();
}
} Cette classe prend simplement le modèle et renvoie un widget de flottement. Vous pouvez également vous abonner aux modifications des propriétés afin que votre widget soit correctement mis à jour lorsque quelque chose se produit sur le modèle. À cet effet, utilisez le flux défini sur chaque propriété ou utilisez la propriété de propriété propertyChanged qui renvoie le flux et émet de la valeur chaque fois qu'une propriété change. Pour redéfinir l'interface utilisateur des composants par défaut à l'intérieur flutter_dynamic_forms_components Définissez simplement vos rendus pour les modèles existants. Vous pouvez même avoir plusieurs rendus et afficher une interface utilisateur différente sur un autre écran. Utilisez le paramètre FormElementRendererFunction renderer de la méthode de rendu pour rendre récursivement les enfants.
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);
},
),
)
],
),
);
}
} Il y a beaucoup de chauffeur lors de la mise en œuvre de classes Parser et Model . Étant donné que la plupart des applications devront probablement créer de nombreux composants personnalisés, il existe également un package de générateur qui vous permet de définir des composants et leurs propriétés en utilisant une syntaxe YAML simple.