عنصر Form ومفتاح GlobalKey
عنصر Form ومفتاح GlobalKey
يوفر Flutter عنصرًا مخصصًا يُدعى Form يعمل كحاوية لمجموعة من حقول الإدخال. بدلًا من إدارة منطق التحقق لكل حقل بشكل مستقل، يتيح لك عنصر Form تنسيق عمليات التحقق والحفظ وإعادة التعيين عبر جميع عناصر FormField الموجودة ضمنه دفعةً واحدة. هذا هو الأسلوب القياسي والمعتمد لبناء أي نموذج إدخال في Flutter.
لماذا نستخدم عنصر Form؟
بدون عنصر Form ستحتاج إلى استدعاء مُحقِّق كل حقل يدويًا، وتتبع ما إذا كانت الحقول قد تمت معالجتها، وإعادة تعيينها واحدًا تلو الآخر. يحل عنصر Form كل هذا عن طريق:
- تجميع عناصر
FormFieldالمرتبطة ضمن عنصر أب منسِّق واحد - كشف كائن
FormStateالذي يحتوي على أساليبvalidate()وsave()وreset() - تشغيل التحقق على جميع الحقول الفرعية في آنٍ واحد عند استدعاء
validate() - استدعاء رد الاستدعاء
onSavedعلى كل حقل عند استدعاءsave() - مسح قيم جميع الحقول ونص الخطأ عند استدعاء
reset()
FormField شيوعًا هي TextFormField، التي تجمع بين TextField ودعم مدمج لـvalidator وonSaved وinitialValue. أي عنصر يرث من FormField<T> يشارك تلقائيًا في عنصر Form الأب.التعرف على GlobalKey<FormState>
للاستطاعة على استدعاء أساليب Form من خارج أسلوب build — مثلًا من onPressed لزر — تحتاج إلى مرجع لكائن FormState الخاص به. يوفر نظام المفاتيح في Flutter هذه الإمكانية. المفتاح GlobalKey<FormState> هو مفتاح فريد يتيح لك، بمجرد إرفاقه بعنصر Form، الوصول إلى FormState الخاص بهذا النموذج في أي وقت عبر _formKey.currentState.
تعريف GlobalKey وإرفاقه بالنموذج
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
// 1. أعلن المفتاح كحقل في فئة State.
// يجب أن يظل ثابتًا عبر عمليات إعادة البناء، لذا يوضع هنا — وليس داخل build().
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Form(
// 2. أرفق المفتاح بعنصر Form.
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(labelText: 'البريد الإلكتروني'),
validator: (value) {
if (value == null || value.isEmpty) return 'الرجاء إدخال بريدك الإلكتروني';
return null; // null يعني صالح
},
),
ElevatedButton(
onPressed: _submit,
child: const Text('إرسال'),
),
],
),
);
}
void _submit() {
// 3. الوصول إلى FormState عبر currentState واستدعاء validate().
if (_formKey.currentState!.validate()) {
// أعادت جميع المُحقِّقات null — النموذج صالح.
_formKey.currentState!.save();
}
}
}
GlobalKey داخل أسلوب build(). في كل مرة يُشغَّل build() سيُنشأ مثيل مفتاح جديد مما يجعل Flutter يعامل Form باعتباره عنصرًا جديدًا كليًا ويفقد حالة جميع الحقول. أعلن المفتاح دائمًا كمتغير مثيل في فئة State الخاصة بك.الأساليب الأساسية الثلاثة لـ FormState
بمجرد حيازة مرجع لـFormState عبر _formKey.currentState، تحصل على وصول لثلاثة أساليب جوهرية:
validate()— يُشغِّل رد استدعاءvalidatorالخاص بكل حقل. يُعيدtrueإذا أعادت جميع المُحقِّقاتnull(أي صالحة)، أوfalseإذا أعاد أحدها نص خطأ. كما يعرض نص الخطأ أسفل الحقول غير الصالحة.save()— يستدعي رد الاستدعاءonSavedعلى كل عنصر فرعي منFormFieldمما يتيح لك جمع القيم الحالية في متغيراتك الخاصة.reset()— يعيد ضبط كل حقل إلىinitialValueالخاص به ويمسح جميع رسائل خطأ التحقق.
استخدام validate() وsave() وreset()
class RegistrationForm extends StatefulWidget {
const RegistrationForm({super.key});
@override
State<RegistrationForm> createState() => _RegistrationFormState();
}
class _RegistrationFormState extends State<RegistrationForm> {
final _formKey = GlobalKey<FormState>();
String _name = '';
String _email = '';
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
decoration: const InputDecoration(labelText: 'الاسم'),
validator: (v) => (v == null || v.trim().isEmpty) ? 'الاسم مطلوب' : null,
onSaved: (v) => _name = v!.trim(),
),
const SizedBox(height: 12),
TextFormField(
decoration: const InputDecoration(labelText: 'البريد الإلكتروني'),
keyboardType: TextInputType.emailAddress,
validator: (v) {
if (v == null || v.isEmpty) return 'البريد الإلكتروني مطلوب';
if (!v.contains('@')) return 'أدخل بريدًا إلكترونيًا صالحًا';
return null;
},
onSaved: (v) => _email = v!,
),
const SizedBox(height: 24),
Row(
children: [
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
// تم تعبئة _name و_email الآن
debugPrint('تسجيل: $_name <$_email>');
}
},
child: const Text('تسجيل'),
),
const SizedBox(width: 12),
TextButton(
// reset() يمسح القيم ورسائل الخطأ
onPressed: () => _formKey.currentState!.reset(),
child: const Text('مسح'),
),
],
),
],
),
);
}
}
فهم autovalidateMode
افتراضيًا لا يعرض Form أخطاء التحقق إلا بعد استدعاء validate() صراحةً. يمكنك تغيير هذا السلوك باستخدام خاصية autovalidateMode على عنصر Form أو على عناصر TextFormField الفردية:
AutovalidateMode.disabled— (الافتراضي) تحقق فقط عند استدعاءvalidate().AutovalidateMode.onUserInteraction— يتحقق من الحقل تلقائيًا بعد أول تفاعل للمستخدم معه.AutovalidateMode.always— يتحقق عند كل إعادة بناء حتى قبل التفاعل (نادرًا ما يُنصح به من منظور تجربة المستخدم).
AutovalidateMode.onUserInteraction الإعداد الأكثر ملاءمةً للمستخدم. فهو يتجنب عرض رسائل الخطأ على نموذج فارغ لم تتم لمسه بعد، لكنه يعطي ملاحظات فورية بمجرد أن يكتب المستخدم شيئًا وينتقل. استخدمه في النماذج التي تريد فيها ملاحظات آنية دون انطباع أول مُربك.ملخص
عنصر Form هو الأساس لجميع عمليات إدخال المستخدم المنظمة في Flutter. يعمل جنبًا إلى جنب مع GlobalKey<FormState> الذي يجب تعريفه كمتغير مثيل دائم في فئة State الخاصة بك. من خلال _formKey.currentState يمكنك استدعاء validate() لتشغيل جميع المُحقِّقات دفعةً واحدة، وsave() لجمع القيم، وreset() لمسح النموذج. يُبقي هذا النمط منطق النموذج مركزيًا وأسلوب build نظيفًا.