フラッターアプリケーションでサーバー駆動型UIのソリューションを提供するフラッターおよびダートライブラリのコレクション。
| パッケージ | パブ |
|---|---|
| expression_language | |
| dynamic_forms | |
| dynamic_forms_generator | |
| flutter_dynamic_forms | |
| flutter_dynamic_forms_components |
このプロジェクトの背後にあるアイデアは、サーバー上のXMLまたはJSONを介してコンポーネントを定義し、アプリを再配置せずにフラッタークライアントで消費できることです。主な焦点は、カスタムコンポーネントとそのプロパティ間の複雑な関係を定義する機能です。たとえば、カスタム検証ルールを定義したり、条件に基づいて可視性を切り替えることができます。これにより、ユーザー入力を収集するフォームで作業する場合に特に役立ちますが、フラッターウィジェットツリーを表示するために使用できます。
作業デモを含む例のプロジェクトも参照してください。
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 >JSONにフォームを説明することを好む場合は、このJSONの例を確認してください。

pubspec.yamlファイルに次の依存関係を追加します。
flutter_dynamic_forms : <latest version>
flutter_dynamic_forms_components : <latest version>flutter_dynamic_forms_componentsライブラリには、 Label 、 CheckBox 、 RadioButtonGroupなどの事前定義されたコンポーネントのセットが含まれています。次の手順を実行する必要があるコンポーネントでアプリを動作させるには:
まず、XMLまたはJSONからフォームデータを取得するかどうかを決定する必要があります。 JsonFormManagerまたはXmlFormManagerいずれかを使用できます。これらのクラスは、フォームを解析することになります。また、DARTのXML/JSONフォームのオブジェクト表現であるゲッターformもあります。また、Formで何かが発生したときにフォームの状態を操作したり、フォームを検証したり、フォームからすべてのデータを収集してサーバーに送信できるように、フォームの状態を操作するなど、フォームで有用な操作を実行することもできます。フォームマネージャーにカスタムロジックを書き込む必要がある場合は、簡単に拡張できます。
class CustomFormManager extends JsonFormManager {
Future < void > sendDataToServer () async {
var properties = getFormProperties ();
// send properties to the server
}
} FormManagerを初期化する最も簡単な方法は、 ParsedFormProviderウィジェットを介してです。 XML/JSONコンテンツ、パーサーのリストを使用すると、 FormManagerインスタンスが作成され、データの解析も担当します。 ParsedFormProviderはフードの下にProviderパッケージを使用しているため、 FormProvider.of<YourFormProvider>(context)を呼び出すことにより、 FormManagerウィジェットサブツリーで利用可能になります。
画面にフォームをレンダリングするには、 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
) FormManagerタイプとして提供することができます: FormRenderer<XmlFormManager>(...)以前に定義されたFormProviderから自動的に解決されるか、特定のFormManagerインスタンスをFormRendererコンストラクターに渡すことができます。
このウィジェットには、各モデルがフラッターウィジェットにどのように翻訳されるかを制御するレンダラーのリストも掲載されています。上記の例では、事前定義されたレンダラーのセットを使用します。 Reactiveという言葉は、各コンポーネントがフォームモデルプロパティの変更を聞き、それ自体を更新することを意味します。
最後のオプションパラメーターはdispatcherです。これにより、レンダリングクラスで作成されたFormElementEventから派生したイベントを処理できます。 dispatcherパラメーターが提供されない場合、型ChangeValueEventタイプのイベントのみが処理され、プロパティ値の変更を引き起こすフォームマネージャーインスタンスに直接委任されます。カスタムイベントを送信する必要がある場合(ボタンクリックなど)、独自のdispatcherハンドラーを使用しますが、フォームChangeValueEventにform managerを処理する必要があります。
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
}サーバーにデータを送り返すプロセスの背後にあるアイデアは、フォーム全体を返送するのではなく、ユーザーによって変更された値のみを送信すべきであるということです。
データを収集するには、単に呼び出します。
List < FormPropertyValue > data = formManager. getFormData ()コンポーネントパーサー定義で可変としてマークされたすべてのプロパティのリストが含まれています。デフォルトのコンポーネントでは、これらはユーザーが変更すると予想されるプロパティです。各アイテムには、ソース要素、プロパティ名、およびプロパティ値のIDが含まれています。通常、フォームを送信するには、このリストをシリアル化してサーバーに送り返します。
flutter_dynamic_forms_componentsパッケージには、単純なフォームアプリケーションに関連する一連の基本コンポーネントのみが含まれています。私の古いアプリにはいくつかの要件があるため、すべてのコンポーネントが直接フラッターウィジェットに対応するわけではありません。それは将来変わるかもしれないと思います。また、コンポーネントを設計するときは、 Labelなどの低レベルコンポーネントまたはUserProfileなどの高レベルコンポーネントをいつでも選択できます。この複雑な高レベルコンポーネントの場合、クライアントアプリケーションが最終ウィジェットの外観を完全に制御できるようにします。これらの理由から、各アプリケーションにコンポーネントのカスタムセットを作成して、各プロパティを完全に制御することをお勧めします。
カスタムコンポーネントを実装するには、 Parser 、 Model 、 Renderer 3つのクラスを提供する必要があります。その後、パーサーとモデルは、上記のコードで見ることができるように、フォームを構築しているときに登録する必要があります。 CheckBox例で示しましょう。
このクラスは、コンポーネントが対応するモデルクラスに脱必要にされる方法を制御します。 XMLとJSONの両方で動作します。 ParserNodeパラメーターには、現在のXML/JSONノードから値を解析できるメソッドのコレクションが含まれています。 ParseメソッドのElementParserFunction parserパラメーターを使用して、子供のノードを再帰的に解析します。
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 ,
);
}
}モデルは、フラッター依存関係のない主要なコンポーネント定義です。コンポーネントは、すべてのプロパティを継承する他のコンポーネントを拡張できます。また、子供としてコンポーネントを含めることもできます。すべてのプロパティには、値に評価される単純な値または式のいずれかを含めることができます。これらのケースの両方をカバーできるようにするには、すべてのプロパティをProperty<T>構文を使用して定義する必要があります。プロパティはpropertiesと呼ばれる単一のマップに保存されるため、コンポーネントツリー全体を簡単に通過できます。このマップの周りにゲッターとセッターを作成して、プロパティ値に簡単にアクセスして設定できることをお勧めします。
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 ();
}
}このクラスは、単にモデルを取り、フラッターウィジェットを返します。また、モデルで何かが発生したときにウィジェットが適切に更新されるように、プロパティの変更を購読することもできます。この目的のために、各プロパティで定義されたストリームを使用するか、プロパティが変更されるたびにストリームを返し、値を発するコンポーネントプロパティpropertyChangedを使用します。 flutter_dynamic_forms_components内のデフォルトコンポーネントのUIを再定義するには、既存のモデルのレンダラーを定義するだけです。複数のレンダラーを持ち、別のUIを別の画面に表示することもできます。 FormElementRendererFunction rendererパラメーターを使用して、子どもを再帰的にレンダリングします。
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);
},
),
)
],
),
);
}
}ParserとModelクラスを実装する際には、多くのボイラープレートがあります。ほとんどのアプリはおそらく多くのカスタムコンポーネントを作成する必要があるため、単純なYAML構文を使用してコンポーネントとそのプロパティを定義できるジェネレーターパッケージもあります。