مجموعة من مكتبات Flutter و DART توفر حلًا لواجهة واجهة المستخدم التي يحركها الخادم في تطبيق Flutter الخاص بك.
| طَرد | حانة |
|---|---|
| Expression_language | |
| Dynamic_Forms | |
| dynamic_forms_generator | |
| flutter_dynamic_forms | |
| flutter_dynamic_forms_components |
تتمثل الفكرة وراء هذا المشروع في أن تكون قادرًا على تحديد مكوناتك عبر XML أو JSON على الخادم واستهلاكها في عميل Flutter دون إعادة نشر التطبيق. ينصب التركيز الرئيسي على القدرة على تحديد المكونات المخصصة والعلاقات المعقدة بين خصائصها. على سبيل المثال ، يمكنك تحديد قواعد التحقق من الصحة المخصصة ، وتبديل الرؤية استنادًا إلى حالة ما ، وما إلى ذلك. هذا يجعلها مفيدة بشكل خاص عند العمل مع النماذج التي تجمع بعض مدخلات المستخدم ولكن يمكن استخدامها لعرض أي شجرة عنصر واجهة مستخدم.
انظر أيضًا Project Example الذي يحتوي على عرض تجريبي.
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 . سوف تهتم هذه الفصول بتوصيل نماذجك. لديهم أيضًا form getter وهو تمثيل كائن لنموذج XML/JSON الخاص بك في DART. يمكنهم أيضًا القيام ببعض العمليات المفيدة في النموذج ، مثل معالجة حالة النموذج عندما يحدث شيء ما في واجهة المستخدم ، أو التحقق من صحة النموذج ، أو جمع جميع البيانات من النموذج حتى يمكن إرساله إلى الخادم. إذا كنت بحاجة إلى كتابة منطق مخصص في FormManager ، فيمكنك تمديده بسهولة:
class CustomFormManager extends JsonFormManager {
Future < void > sendDataToServer () async {
var properties = getFormProperties ();
// send properties to the server
}
} أسهل طريقة لتهيئة FormManager الخاص بك هي عن طريق جهاز ParsedFormProvider . سيستغرق محتوى XML/JSON الخاص بك ، وقائمة المحللين ، وسيقوم بإنشاء مثيل FormManager ويعتني أيضًا بتحليل بياناتك. يستخدم ParsedFormProvider حزمة Provider تحت الغطاء ، وبالتالي فإن FormManager سيكون متاحًا في الشجرة الفرعية عن واجهة المستخدم عن طريق الاتصال FormProvider.of<YourFormProvider>(context) .
لتقديم النموذج الخاص بك على الشاشة ، يمكنك استخدام أداة 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 .
تأخذ هذه القطعة أيضًا قائمة من العارضين الذين يتحكمون في كيفية ترجمة كل طراز إلى أداة الرفرفة. في المثال أعلاه ، نستخدم مجموعة من العارضين المحددة مسبقًا. تعني الكلمة التفاعلية أن كل مكون سيستمع إلى التغييرات في خاصية نموذج النموذج وسيقوم بتحديث نفسه.
المعلمة الاختيارية الأخيرة هي dispatcher . يسمح لك بالتعامل مع الأحداث المستمدة من FormElementEvent المنتجة في فصول التقديم الخاصة بك. عندما لا يتم توفير معلمة dispatcher ، تتم معالجة أحداث Type ChangeValueEvent فقط وتفويضها مباشرة إلى مثيل FormManager الذي يسبب تغييرات في قيم الممتلكات. استخدم معالج dispatcher الخاص بك إذا كنت بحاجة إلى إرسال أحداث مخصصة (مثل زر النقر) ، ولكن يجب عليك دائمًا السماح لمدير النماذج بالتعامل مع 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
}تتمثل الفكرة وراء عملية إرسال البيانات إلى الخادم في أنه لا ينبغي لنا إعادة إرسال النموذج بأكمله ولكن فقط القيم التي تم تغييرها من قبل المستخدم.
لجمع البيانات ببساطة الاتصال:
List < FormPropertyValue > data = formManager. getFormData ()أنه يحتوي على قائمة بجميع الخصائص التي تم تمييزها على أنها قابلة للتغيير في تعريف المحلل. في المكونات الافتراضية ، هذه هي الخصائص التي من المتوقع تغييرها من قبل المستخدم. يحتوي كل عنصر على معرف العنصر المصدر واسم الخاصية وقيمة الخاصية. لإرسال النموذج الذي ترغب عادة في تسلسل هذه القائمة وإرسالها مرة أخرى إلى الخادم الخاص بك.
تحتوي حزمة flutter_dynamic_forms_components على مجموعة فقط من المكونات الأساسية المتعلقة بتطبيق نموذج بسيط. نظرًا لبعض المتطلبات على تطبيقي القديم ، لا تتوافق كل المكونات التي تسميها مباشرة مع أجهزة توجيه الرفرفة. أعتقد أن هذا قد يتغير في المستقبل. أيضًا عند تصميم المكونات ، يمكنك دائمًا الاختيار بين المكونات ذات المستوى المنخفض مثل Label أو المكون عالي المستوى مثل UserProfile . في حالة هذا المكون المعقد عالي المستوى ، تدع تطبيق العميل يتحكم تمامًا في مظهر القطعة النهائية. لهذه الأسباب ، أود أن أوصي بكل تطبيق لكتابة مجموعة مخصصة من المكونات الخاصة به لتحكم كامل في كل خاصية.
لتنفيذ مكون مخصص تحتاج إلى توفير 3 فئات: Parser ، Model ، Renderer . بعد ذلك ، يجب تسجيل المحلات والموديلات عند إنشاء النموذج كما ترون في الكود أعلاه. دعنا نعرضه على مثال CheckBox :
يتحكم هذا الفئة في كيفية إلحاق المكون في فئة نموذج مقابلة. إنه يعمل على كل من XML و JSON. تحتوي المعلمة ParserNode على مجموعة من الطرق التي تتيح لك تحليل القيم من عقدة XML/JSON الحالية. استخدم معلمة 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 بحيث يمكنك بسهولة اجتياز شجرة المكون بأكملها. إنها فكرة جيدة إنشاء Getters والمستقبين حول هذه الخريطة حتى تتمكن من الوصول بسهولة وضبط قيم الخصائص.
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 ();
}
} يأخذ هذا الفئة ببساطة النموذج ويعيد عنصر واجهة مستخدم رفرفة. يمكنك أيضًا الاشتراك في التغييرات في الخصائص بحيث يتم تحديث عنصر واجهة المستخدم بشكل صحيح عندما يحدث شيء ما على النموذج. لهذا الغرض ، استخدم الدفق المحدد على كل خاصية أو استخدم Property Property propertyChanged الذي يعيد الدفق وينبعث قيمة كلما تغيرت أي خاصية. لإعادة تعريف واجهة المستخدم للمكونات الافتراضية داخل flutter_dynamic_forms_components ببساطة تحدد العارضين الخاصة بك للنماذج الموجودة. يمكنك حتى الحصول على العديد من العارضين وإظهار واجهة مستخدم مختلفة على شاشة مختلفة. استخدم معلمة 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 البسيط.