Una colección de bibliotecas Flutter y Dart que proporciona una solución para la interfaz de usuario impulsada por el servidor en su aplicación Flutter.
| Paquete | Pub |
|---|---|
| Expression_Language | |
| Dynamic_forms | |
| Dynamic_Forms_Generator | |
| flutter_dynamic_forms | |
| flutter_dynamic_forms_components |
La idea detrás de este proyecto es poder definir sus componentes a través de XML o JSON en el servidor y consumirlo en el cliente Flutter sin redistribuir la aplicación. El enfoque principal está en la capacidad de definir componentes personalizados y relaciones complejas entre sus propiedades. Por ejemplo, puede definir reglas de validación personalizadas, alternar la visibilidad basada en una condición, etc. Esto lo hace especialmente útil cuando se trabaja con formularios recolectando alguna entrada del usuario, pero se puede usar para mostrar cualquier árbol de widget de Flutter.
También vea el proyecto de ejemplo que contiene una demostración de trabajo.
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 prefiere que JSON describiera su formulario, consulte este ejemplo de JSON.

Agregue las siguientes dependencias a su archivo pubspec.yaml :
flutter_dynamic_forms : <latest version>
flutter_dynamic_forms_components : <latest version> La biblioteca flutter_dynamic_forms_components contiene un conjunto de componentes predefinidos como Label , CheckBox , RadioButtonGroup , etc. para que su aplicación funcione con esos componentes que necesita para realizar los siguientes pasos:
Primero, debe decidir si desea obtener sus datos de formulario de XML o JSON. Puede usar JsonFormManager o XmlFormManager . Esas clases se encargarán de analizar sus formas. También tienen una form de obtener que es la representación del objeto de su formulario XML/JSON en DART. También pueden realizar una operación útil en el formulario, como manipular el estado del formulario cuando algo sucede en la interfaz de usuario, validar el formulario o recopilar todos los datos del formulario para que se pueda enviar al servidor. Si necesita escribir una lógica personalizada en FormManager, puede extenderla fácilmente:
class CustomFormManager extends JsonFormManager {
Future < void > sendDataToServer () async {
var properties = getFormProperties ();
// send properties to the server
}
} La forma más fácil de inicializar su FormManager es a través de ParsedFormProvider Widget. Tomará su contenido XML/JSON, lista de analizadores y creará la instancia FormManager y también se encargará de analizar sus datos. ParsedFormProvider está utilizando el paquete Provider debajo del capó, por lo que el FormManager estará disponible en su subárbol de widget llamando FormProvider.of<YourFormProvider>(context) .
Para representar su formulario en la pantalla, puede usar el 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
) Puede proporcionar su FormManager como un tipo: FormRenderer<XmlFormManager>(...) y se resolverá automáticamente desde FormProvider anteriormente definido previamente o puede pasar una instancia FormManager específica al constructor FormRenderer .
Este widget también toma una lista de renderizadores que controla cómo cada modelo se traduciría al widget de Flutter. En el ejemplo anterior, usamos un conjunto de renderistas predefinidos. La palabra reactiva significa que cada componente escuchará los cambios en la propiedad del modelo de formulario y se actualizará.
El último parámetro opcional es un dispatcher . Le permite manejar eventos derivados de FormElementEvent producidos en sus clases de renderizado. Cuando no se proporciona el parámetro dispatcher , solo los eventos de tipo ChangeValueEvent tipo se procesan y delegan directamente a la instancia de FormManager que causan cambios de los valores de la propiedad. Use su propio controlador dispatcher si necesita enviar eventos personalizados (como un clic de botón), pero siempre debe dejar que Form Manager maneje el 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
}La idea detrás del proceso de enviar datos al servidor es que no debemos enviar todo el formulario, sino solo los valores cambiados por el usuario.
Para recopilar los datos, simplemente llame:
List < FormPropertyValue > data = formManager. getFormData ()Contiene una lista de todas las propiedades que se marcaron como un mutable en una definición de analizador de componentes. En los componentes predeterminados, esas son las propiedades que se espera que un usuario cambie. Cada elemento contiene la ID del elemento fuente, el nombre de la propiedad y el valor de la propiedad. Para enviar el formulario, generalmente desea serializar esta lista y enviarlo de nuevo a su servidor.
El paquete flutter_dynamic_forms_components contiene solo un conjunto de componentes básicos relacionados con una aplicación de formulario simple. Debido a algunos requisitos en mi aplicación anterior, no todos los componentes que nombran directamente a los widgets de Flutter. Creo que eso puede cambiar en el futuro. Además, al diseñar componentes, siempre puede elegir entre componentes de bajo nivel como Label o componente de alto nivel como UserProfile . En el caso de este componente complejo de alto nivel, permite que la aplicación del cliente controle completamente el aspecto del widget final. Por esas razones, recomendaría que cada aplicación escriba su conjunto personalizado de componentes para tener un control completo sobre cada propiedad.
Para implementar un componente personalizado, debe proporcionar 3 clases: Parser , Model y Renderer . Los analizadores y los modelos deben registrarse cuando está construyendo el formulario como puede ver en el código anterior. Vamos a mostrarlo en el ejemplo CheckBox :
Esta clase controla cómo el componente se deserializaría en una clase modelo correspondiente. Funciona tanto en XML como en JSON. El parámetro ParserNode contiene una colección de métodos que le permiten analizar los valores del nodo XML/JSON actual. Utilice el ElementParserFunction parser la función de elemento del método de análisis para analizar recursivamente los nodos de los niños.
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 ,
);
}
} El modelo es la definición de componente principal sin dependencia de Flutter. Un componente puede extender otro componente heredando todas las propiedades. También puede contener componentes como sus hijos. Cada propiedad puede contener valor o expresión simple que se evalúa al valor. Para poder cubrir ambos casos, todas las propiedades deben definirse utilizando la sintaxis Property<T> . Las propiedades se almacenan en un solo mapa llamado properties para que pueda atravesar fácilmente todo el árbol de componentes. Es una buena idea crear Getters y Setters en este mapa para que pueda acceder fácilmente y establecer valores de propiedad.
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 ();
}
} Esta clase simplemente toma el modelo y devuelve un widget Flutter. También puede suscribirse a los cambios en las propiedades para que su widget se actualice correctamente cuando algo sucede en el modelo. Para este propósito, use el flujo definido en cada propiedad o use la propertyChanged componente que devuelve el flujo y emite valor cada vez que cambia cualquier propiedad. Para redefinir la interfaz de usuario de los componentes predeterminados dentro de flutter_dynamic_forms_components simplemente defina sus renderistas para los modelos existentes. Incluso puede tener múltiples renderizadores y mostrar una interfaz de usuario diferente en una pantalla diferente. Use el parámetro FormElementRendererFunction renderer del método de renderizado para representar recursivamente a los niños.
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);
},
),
)
],
),
);
}
} Hay muchas calderas al implementar clases Parser y Model . Debido a que la mayoría de las aplicaciones probablemente necesitarán crear muchos componentes personalizados, también hay un paquete de generador que le permite definir componentes y sus propiedades utilizando una sintaxis YAML simple.