Uma coleção de bibliotecas de vibração e dardo fornecendo uma solução para a interface do usuário acionada por servidor no seu aplicativo Flutter.
| Pacote | Pub |
|---|---|
| Expression_Language | |
| dinâmico_forms | |
| dinâmico_forms_generator | |
| flutter_dynamic_forms | |
| flutter_dynamic_forms_components |
A idéia por trás deste projeto é poder definir seus componentes via XML ou JSON no servidor e consumi -lo no Flutter Client sem reimplantar o aplicativo. O foco principal é a capacidade de definir componentes personalizados e relações complexas entre suas propriedades. Por exemplo, você pode definir regras de validação personalizadas, alternar a visibilidade com base em uma condição, etc. Isso o torna especialmente útil ao trabalhar com formulários coletando alguma entrada do usuário, mas pode ser usada para exibir qualquer árvore de widgets de vibração.
Veja também o exemplo de projeto que contém uma demonstração de trabalho.
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 >Se você preferir JSON para descrever seu formulário, verifique este exemplo json.

Adicione as seguintes dependências ao seu arquivo pubspec.yaml :
flutter_dynamic_forms : <latest version>
flutter_dynamic_forms_components : <latest version> A biblioteca flutter_dynamic_forms_components contém um conjunto de componentes predefinidos, como Label , CheckBox , RadioButtonGroup etc. Para fazer seu aplicativo funcionar com esses componentes necessários para executar as seguintes etapas:
Primeiro, você precisa decidir se deseja obter seus dados de formulário de XML ou JSON. Você pode usar JsonFormManager ou XmlFormManager . Essas aulas cuidarão de analisar seus formulários. Eles também têm uma form getter, que é a representação do objeto do seu formulário XML/JSON no DART. Eles também podem executar alguma operação útil no formulário, como manipular o estado do formulário quando algo acontece na interface do usuário, validando o formulário ou coletando todos os dados do formulário para que possam ser enviados de volta ao servidor. Se você precisar escrever lógica personalizada no formulário Formanager, poderá estendê -la facilmente:
class CustomFormManager extends JsonFormManager {
Future < void > sendDataToServer () async {
var properties = getFormProperties ();
// send properties to the server
}
} A maneira mais fácil de inicializar o seu FormManager é via widget ParsedFormProvider . Ele levará seu conteúdo XML/JSON, lista de analisadores e criará a instância FormManager e também cuida de analisar seus dados. ParsedFormProvider está usando o pacote Provider sob o capô, para que o FormManager esteja disponível no seu subárvore do seu widget ligando para FormProvider.of<YourFormProvider>(context) .
Para renderizar seu formulário na tela, você pode usar o 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
) Você pode fornecer seu FormManager como um tipo: FormRenderer<XmlFormManager>(...) e ele será resolvido automaticamente do FormProvider definido anteriormente ou poderá passar uma instância específica FormManager para o construtor FormRenderer .
Esse widget também leva uma lista de renderizadores que controla como cada modelo seria traduzido para o widget fleta. No exemplo acima, usamos um conjunto de renderizadores predefinidos. A palavra reativa significa que cada componente ouvirá as alterações na propriedade Model Form e se atualizará.
O último parâmetro opcional é um dispatcher . Ele permite que você lide com eventos derivados do FormElementEvent produzido em suas aulas de renderização. Quando o parâmetro dispatcher não é fornecido, apenas os eventos do tipo ChangeValueEvent são processados e delegados diretamente na instância do formanador, causando alterações dos valores da propriedade. Use o seu próprio manipulador dispatcher se precisar enviar eventos personalizados (como um clique de botão), mas você sempre deve permitir que o gerente do formulário lide com o 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
}A idéia por trás do processo de envio de dados de volta ao servidor é que não devemos enviar de volta o formulário inteiro, mas apenas os valores foram alterados pelo usuário.
Para coletar os dados, simplesmente ligue:
List < FormPropertyValue > data = formManager. getFormData ()Ele contém uma lista de todas as propriedades que foram marcadas como um mutável em uma definição de analisador de componentes. Nos componentes padrão, essas são as propriedades que devem ser alteradas por um usuário. Cada item contém o ID do elemento de origem, nome da propriedade e valor da propriedade. Para enviar o formulário, você geralmente deseja serializar esta lista e enviá -lo de volta ao seu servidor.
O pacote flutter_dynamic_forms_components contém apenas um conjunto de componentes básicos relacionados a um aplicativo de formulário simples. Devido a alguns requisitos no meu aplicativo antigo, nem todos os componentes nomeam diretamente os widgets do fleto. Eu acho que isso pode mudar no futuro. Além disso, ao projetar componentes, você sempre pode escolher entre componentes de baixo nível, como Label ou componente de alto nível, como UserProfile . No caso deste complexo componente de alto nível, você deixa o aplicativo cliente controlar completamente a aparência do widget final. Por esses motivos, eu recomendaria que cada aplicativo escreva seu conjunto personalizado de componentes para ter controle completo sobre cada propriedade.
Para implementar um componente personalizado, você precisa fornecer 3 classes: Parser , Model e Renderer . Os analisadores e os modelos precisam ser registrados quando você estiver criando o formulário, como pode ver no código acima. Vamos mostrar no exemplo CheckBox :
Esta classe controla como o componente seria desserializado em uma classe de modelo correspondente. Funciona em XML e JSON. O parâmetro ParserNode contém uma coleção de métodos que permitem analisar os valores do nó XML/JSON atual. Use o parâmetro ElementParserFunction parser do método parse para analisar os nós de crianças recursivamente.
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 ,
);
}
} O modelo é a definição principal do componente sem qualquer dependência de vibração. Um componente pode estender outros componentes herdando todas as propriedades. Também pode conter componentes como filhos. Cada propriedade pode conter um valor ou expressão simples que é avaliado com o valor. Para poder cobrir os dois casos, todas as propriedades devem ser definidas usando a sintaxe Property<T> . As propriedades são armazenadas em um único mapa chamado properties para que você possa atravessar facilmente toda a árvore de componentes. É uma boa ideia criar getters e setters em torno deste mapa para que você possa acessar e definir facilmente os valores de propriedades.
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 classe simplesmente pega o modelo e retorna um widget de vibração. Você também pode se inscrever nas alterações nas propriedades para que seu widget seja atualizado corretamente quando algo acontecer no modelo. Para esse fim, use o fluxo definido em cada propriedade ou use a propriedade do componente propertyChanged que retorna o fluxo e emite valor sempre que qualquer propriedade mudar. Para redefinir a interface do usuário dos componentes padrão dentro flutter_dynamic_forms_components simplesmente definem seus renderizadores para os modelos existentes. Você pode até ter vários renderizadores e mostrar uma interface do usuário diferente em uma tela diferente. Use o parâmetro FormElementRendererFunction renderer do método de renderização para renderizar crianças.
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);
},
),
)
],
),
);
}
} Há muita caldeira ao implementar Parser e as classes Model . Como a maioria dos aplicativos provavelmente precisará criar muitos componentes personalizados, também há um pacote gerador que permite definir componentes e suas propriedades usando a sintaxe YAML simples.