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

التحقق المتزامن من المدخلات

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

التحقق المتزامن من المدخلات

كل TextFormField (وأي ودجت يقبل FormField) يعرض خاصية validator وهي دالة رد نداء. تتلقى هذه الدالة القيمة الحالية للحقل كـ String? ويجب أن تُعيد String يحتوي على رسالة الخطأ عندما تكون القيمة غير صالحة، أو null عندما تكون القيمة صحيحة. يعرض Flutter النص المُعاد كنص خطأ للحقل ويعلم النموذج على أنه غير صالح. يحدث هذا بشكل متزامن — تعمل الدالة على الخيط الرئيسي ويجب أن تنتهي فوراً دون أي عمليات إدخال/إخراج غير متزامنة.

ملاحظة: يعمل المُتحقق عند استدعاء FormState.validate() بشكل صريح، أو تلقائياً عند كل تغيير عندما يكون autovalidateMode مضبوطاً على AutovalidateMode.onUserInteraction. الوضع الافتراضي هو disabled، لذا لا يبدأ التحقق إلا عند استدعاء validate().

توقيع دالة التحقق

نوع معامل المُتحقق في TextFormField هو:

String? Function(String? value)?

القيمة قابلة للإشارة الفارغة لأن الحقل قد يكون فارغاً. يجب أن يتعامل المُتحقق مع حالة null بشكل صريح إذا كان الحقل قد يكون فارغاً. أعد سلسلة غير فارغة للإشارة إلى وجود خطأ؛ وأعد null للإشارة إلى النجاح.

التحقق من الحقل المطلوب

أبسط مُتحقق يضمن عدم كون الحقل فارغاً. قم بإزالة المسافات البيضاء أولاً حتى تُعامَل القيمة التي تتكون من مسافات فقط على أنها فارغة:

TextFormField(
  decoration: const InputDecoration(labelText: 'الاسم الكامل'),
  validator: (value) {
    if (value == null || value.trim().isEmpty) {
      return 'الاسم الكامل مطلوب';
    }
    return null; // صالح
  },
)

استدعاء value.trim().isEmpty بدلاً من value.isEmpty يمنع سلسلة مثل ' ' من اجتياز التحقق. قم دائماً بإزالة المسافات البيضاء قبل التحقق من الفراغ.

قيود الطول

تحتوي كثير من الحقول على متطلبات حد أدنى أو أقصى للأحرف. قم بتسلسل فحوصات الطول بعد فحص الحقل المطلوب حتى تظل رسائل الخطأ متمايزة وقابلة للتنفيذ:

TextFormField(
  decoration: const InputDecoration(labelText: 'اسم المستخدم'),
  validator: (value) {
    if (value == null || value.trim().isEmpty) {
      return 'اسم المستخدم مطلوب';
    }
    final trimmed = value.trim();
    if (trimmed.length < 3) {
      return 'يجب أن يكون اسم المستخدم 3 أحرف على الأقل';
    }
    if (trimmed.length > 20) {
      return 'لا يمكن أن يتجاوز اسم المستخدم 20 حرفاً';
    }
    return null;
  },
)
نصيحة: قم بتخزين القيمة بعد إزالة المسافات في متغير محلي داخل المُتحقق (كما هو موضح أعلاه) لتجنب استدعاء trim() عدة مرات. يجعل هذا المنطق نظيفاً ويتجنب أخطاء حساب الطول الناتجة عن مقارنة تمثيلات مختلفة لنفس المدخل.

القواعد القائمة على التعابير النمطية

للتنسيقات المنظمة كعناوين البريد الإلكتروني وأرقام الهواتف والرموز البريدية، استخدم RegExp للتحقق من النمط. تُعيد RegExp.hasMatch() في Dart قيمة منطقية تشير إلى ما إذا كان النمط يتطابق في أي مكان في السلسلة. استخدم المراسي (^ و$) لضمان تطابق السلسلة بأكملها، وليس مجرد سلسلة فرعية:

TextFormField(
  decoration: const InputDecoration(labelText: 'البريد الإلكتروني'),
  keyboardType: TextInputType.emailAddress,
  validator: (value) {
    if (value == null || value.trim().isEmpty) {
      return 'البريد الإلكتروني مطلوب';
    }
    // نمط مبسط متوافق مع RFC-5321
    final emailRegex = RegExp(
      r'^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$',
    );
    if (!emailRegex.hasMatch(value.trim())) {
      return 'أدخل عنوان بريد إلكتروني صالحاً';
    }
    return null;
  },
)

تنطبق نفس التقنية على أرقام الهواتف والرموز البريدية وأي حقل مقيد بنمط آخر. عرّف RegExp كـ ثابت على مستوى المكتبة أو ثابت ساكن بدلاً من إنشائه داخل الدالة الرد نداء في كل ضغطة مفتاح — إن تجميع RegExp ليس مجانياً.

دمج قواعد متعددة

يمكن لمُتحقق واحد أن يُنفذ عدة قواعد بترتيب الأولوية. أعد أول خطأ يُواجه حتى يرى المستخدم رسالة واحدة قابلة للتنفيذ في كل مرة:

// RegExp ساكن قابل لإعادة الاستخدام — يُجمَّع مرة واحدة عند تحميل الصنف
static final _passwordRegex = RegExp(
  r'^(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%^&*]).{8,}$',
);

String? _validatePassword(String? value) {
  if (value == null || value.isEmpty) {
    return 'كلمة المرور مطلوبة';
  }
  if (value.length < 8) {
    return 'يجب أن تكون كلمة المرور 8 أحرف على الأقل';
  }
  if (!_passwordRegex.hasMatch(value)) {
    return 'يجب أن تحتوي كلمة المرور على حرف كبير ورقم وحرف خاص';
  }
  return null;
}

// في شجرة الودجات:
TextFormField(
  obscureText: true,
  decoration: const InputDecoration(labelText: 'كلمة المرور'),
  validator: _validatePassword,
)
تحذير: لا تُطلق أبداً طلبات شبكة أو عمليات بحث في قاعدة البيانات أو أي عمل غير متزامن آخر داخل مُتحقق متزامن. يجب أن تُعيد دالة validator بشكل متزامن. للفحوصات التي تتطلب عمليات إدخال/إخراج (مثل "هل اسم المستخدم هذا مأخوذ؟")، استخدم استدعاءً غير متزامن منفصلاً يُشغَّل بواسطة onChanged أو زر "التحقق من التوفر" مخصص، ثم قم بتخزين النتيجة في الحالة والإشارة إليها من المُتحقق المتزامن.

تفعيل التحقق

قم بتغليف حقولك في ودجت Form واحتفظ بـ GlobalKey<FormState>. استدعِ _formKey.currentState!.validate() لتشغيل جميع المُتحققات دفعة واحدة. تُعيد true إذا أعاد كل مُتحقق null:

final _formKey = GlobalKey<FormState>();

void _submit() {
  if (_formKey.currentState!.validate()) {
    // جميع المُتحققات أعادت null — النموذج صالح
    _formKey.currentState!.save();
    // استمر مع بيانات النموذج
  }
}

// في طريقة البناء:
Form(
  key: _formKey,
  child: Column(
    children: [
      TextFormField(validator: _validateEmail, ...),
      TextFormField(validator: _validatePassword, ...),
      ElevatedButton(
        onPressed: _submit,
        child: const Text('إرسال'),
      ),
    ],
  ),
)

الملخص

المُتحققات المتزامنة هي الأداة الأساسية للتحقق من النماذج من جانب العميل في Flutter. اكتب دالة رد نداء من النوع String? Function(String?)، وأعد سلسلة خطأ وصفية لأي شرط غير صالح، وأعد null للمدخلات الصالحة. سلسل فحوصات الحقل المطلوب وقيود الطول وقواعد التعابير النمطية بهذا الترتيب. ابقِ المُتحققات نقية ومتزامنة — أي عمل غير متزامن ينتمي خارج دالة رد نداء المُتحقق. أرفق المُتحققات عبر TextFormField.validator، وفعّلها باستدعاء FormState.validate().