النماذج والتحقق ومدخلات المستخدم

عنصر Form ومفتاح GlobalKey

15 دقيقة الدرس 1 من 12

عنصر 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 نظيفًا.