31 مايو 2025·6 دقيقة قراءة

Kotlin MVI مقابل MVVM لتطبيقات Android ذات النماذج الكثيفة: حالات واجهة المستخدم

مقارنة Kotlin MVI و MVVM لتطبيقات Android التي تعتمد على نماذج كثيفة: طرق عملية لنمذجة التحقق، الواجهة المتفائلة، حالات الخطأ، والمسودات دون اتصال.

Kotlin MVI مقابل MVVM لتطبيقات Android ذات النماذج الكثيفة: حالات واجهة المستخدم

لماذا تتعقّد تطبيقات Android ذات النماذج الكثيفة بسرعة

تطبيقات النماذج الكثيفة تبدو بطيئة أو هشة لأن المستخدمين ينتظرون باستمرار قرارات صغيرة على مستوى التطبيق: هل الحقل صالح، هل نجح الحفظ، هل نعرض خطأ، وماذا يحدث إذا انقطعت الشبكة.

النماذج تكشف أخطاء الحالة أولًا لأنها تمزج بين أنواع عدة من الحالة في آن واحد: حالة الواجهة (ما هو مرئي)، حالة الإدخال (ما كتبه المستخدم)، حالة الخادم (ما تم حفظه)، والحالة المؤقتة (ما هو جاري). عندما تنحرف هذه عن التزامن، يبدأ التطبيق بالشعور "بعشوائية": الأزرار تُعطّل في أوقات خاطئة، تبقى أخطاء قديمة، أو تعيد الشاشة الضبط بعد تدويرها.

تتركز معظم المشاكل في أربعة نطاقات: التحقق (لا سيما قواعد عبر الحقول)، واجهة المستخدم المتفائلة (استجابة سريعة أثناء تنفيذ العمل)، معالجة الأخطاء (فشل واضح وقابل للاسترداد)، والمسودات دون اتصال (لا تفقد العمل غير المكتمل).

تجربة مستخدم جيدة للنماذج تتبع قواعد بسيطة:

  • يجب أن يكون التحقق مفيدًا وقريبًا من الحقل. لا تمنع الكتابة. كن صارمًا عندما يهم الأمر، عادة عند الإرسال.
  • واجهة المستخدم المتفائلة يجب أن تعكس إجراء المستخدم فورًا، لكنها تحتاج أيضًا إلى تراجع نظيف إذا رفضها الخادم.
  • يجب أن تكون الأخطاء محددة وقابلة للتنفيذ، وألا تمسح مدخلات المستخدم.
  • يجب أن تظل المسودات بعد إعادة التشغيل، الانقطاعات، والاتصالات السيئة.

لهذا السبب تصبح مناقشات البنية حادة للنماذج. النمط الذي تختاره يحدد مدى توقعية تلك الحالات تحت الضغط.

لمحة سريعة: MVVM و MVI بمصطلحات بسيطة

الفرق الحقيقي بين MVVM و MVI هو كيف يتدفق التغيير عبر الشاشة.

MVVM (Model View ViewModel) عادةً يبدو كالتالي: يحتفظ ViewModel ببيانات الشاشة، يكشفها للواجهة (غالبًا عبر StateFlow أو LiveData)، ويقدّم أساليب مثل الحفظ، التحقق، أو التحميل. تستدع الواجهة وظائف ViewModel عندما يتفاعل المستخدم.

MVI (Model View Intent) عادةً يكون هكذا: ترسل الواجهة أحداثًا (intents)، يقوم مخفّض (reducer) بمعالجتها، وتُعرض الشاشة من كائن حالة واحد يمثل كل ما تحتاجه الواجهة الآن. تؤدي الآثار الجانبية (الشبكة، قاعدة البيانات) بطريقة محكومة وتُبلغ النتائج مرة أخرى كأحداث.

طريقة بسيطة لتذكر الفكرة الذهنية:

  • MVVM يسأل: "ما البيانات التي يجب أن يكشفها ViewModel، وما الأساليب التي يجب أن يقدمها؟"
  • MVI يسأل: "ما الأحداث التي يمكن أن تحدث، وكيف تحول حالة إلى أخرى؟"

كل نمط يعمل بشكل جيد للشاشات البسيطة. عندما تضيف تحقق عبر الحقول، الحفظ التلقائي، إعادة المحاولة، والمسودات دون اتصال، تحتاج قواعد أشد حول من يمكنه تغيير الحالة ومتى. MVI يفرض تلك القواعد افتراضيًا. MVVM يمكن أن ينجح أيضًا، لكنه يحتاج إلى انضباط: مسارات تحديث متسقة ومعالجة دقيقة للأحداث لمرة واحدة (snackbars، التنقل).

كيف نمذج حالة النموذج بدون مفاجآت

أسرع طريقة لفقدان السيطرة هي ترك بيانات النموذج تعيش في أماكن كثيرة: ربطات العرض، تدفقات متعددة، و"علم منطقي واحد إضافي". شاشات النماذج الكثيفة تظل متوقعة عندما يكون هناك مصدر واحد للحقيقة.

شكل عملي لـ FormState

هدفك أن يكون هناك FormState واحد يحفظ المدخلات الخام بالإضافة إلى بضعة علامات مشتقة يمكنك الوثوق بها. اجعله بسيطًا وكاملاً، حتى لو بدا أكبر قليلًا.

data class FormState(
  val fields: Fields,
  val fieldErrors: Map<FieldId, String> = emptyMap(),
  val formError: String? = null,
  val isDirty: Boolean = false,
  val isValid: Boolean = false,
  val submitStatus: SubmitStatus = SubmitStatus.Idle,
  val draftStatus: DraftStatus = DraftStatus.NotSaved
)

sealed class SubmitStatus { object Idle; object Saving; object Saved; data class Failed(val msg: String) }
sealed class DraftStatus { object NotSaved; object Saving; object Saved }

هذا يبقي التحقق على مستوى الحقل منفصلًا عن مشاكل مستوى النموذج (مثل "المجموع يجب أن يكون > 0"). ينبغي حساب العلامات المشتقة مثل isDirty و isValid في مكان واحد، لا إعادة تنفيذها في الواجهة.

نموذج ذهني نظيف: الحقول (ما كتبه المستخدم)، التحقق (ما الخطأ)، الحالة (ما الذي يفعله التطبيق)، القذارة (ما تغيّر منذ آخر حفظ)، والمسودات (هل يوجد نسخة محلية محفوظة).

أين توضع التأثيرات لمرة واحدة

النماذج تُطلق أيضًا أحداثًا لمرة واحدة: snackbars، التنقل، لافتات "تم الحفظ". لا تضعها داخل FormState وإلا ستتكرر عند التدوير أو عند إعادة الاشتراك بالواجهة.

في MVVM، أرسل التأثيرات عبر قناة منفصلة (مثل SharedFlow). في MVI، نمذجها كـ Effects (أو Events) تستهلكها الواجهة مرة واحدة. هذا الفصل يمنع الأخطاء الشبحية والرسائل المكررة للنجاح.

تدفق التحقق في MVVM مقابل MVI

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

القواعد البسيطة والمتزامنة (الحقول المطلوبة، الحد الأدنى للطول، نطاق الأعداد) يجب أن تُشغّل في ViewModel أو طبقة الدومين، لا في الواجهة. هذا يبقي القواعد قابلة للاختبار ومتسقة.

القواعد غير المتزامنة (مثل "هل هذا البريد مأخوذ؟") أكثر تعقيدًا. تحتاج للتعامل مع التحميل، النتائج القديمة، وحالة "المستخدم كتب مرة أخرى".

في MVVM، يصبح التحقق غالبًا خليطًا من الحالة والطرق المساعدة: ترسل الواجهة تغييرات (تحديثات نص، فقدان التركيز، نقرات الإرسال) إلى ViewModel؛ يحدث ViewModel تحديثًا على StateFlow/LiveData ويكشف أخطاء كل حقل وcanSubmit مشتقة. تبدأ الفحوصات غير المتزامنة عادة وظيفة، ثم تحدث علم تحميل وخطأ عندما تكتمل.

في MVI، يميل التحقق لأن يكون أكثر وضوحًا. تقسيم عملي للمسؤوليات:

  • يقوم المخفّض بالتحقق المتزامن ويحدث أخطاء الحقول فورًا.
  • ينفّذ تأثير تحقق غير متزامن ويبعث نتيجة كنية حدث.
  • يطبّق المخفّض تلك النتيجة فقط إذا كانت لا تزال تتطابق مع الإدخال الأخير.

الخطوة الأخيرة مهمة. إذا كتب المستخدم بريدًا جديدًا بينما فحصت عملية "فريدية البريد"، يجب ألا تكتب النتائج القديمة فوق الإدخال الحالي. غالبًا ما يجعل MVI ذلك أسهل لأنك تستطيع حفظ آخر قيمة مُتحقق منها في الحالة وتجاهل الاستجابات القديمة.

واجهة مستخدم متفائلة وحفظ غير متزامن

Validate UX before Kotlin code
Draft your mobile UI and workflow, then generate native apps when it feels right.
Build Mobile App

الواجهة المتفائلة تعني أن الشاشة تتصرف كما لو أن الحفظ نجح قبل وصول رد الشبكة. في نموذج، غالبًا ما يعني ذلك أن زر الحفظ يتحول إلى "Saving..."، يظهر مؤشر صغير "Saved" عند الانتهاء، وتبقى الحقول قابلة للاستخدام (أو تُقفل عمدًا) أثناء تنفيذ الطلب.

في MVVM، يُنفّذ هذا عادة بتبديل أعلام مثل isSaving، lastSavedAt، و saveError. الخطر هو الانحراف: الحفظات المتداخلة يمكن أن تترك هذه الأعلام غير متسقة. في MVI، يقوم المخفّض بتحديث كائن حالة واحد، لذا فرص حدوث تناقض بين "Saving" و"Disabled" أقل.

لتجنب الإرسال المزدوج وحالات التنافس، اعتبر كل حفظ حدثًا معرفًا. إذا نقر المستخدم حفظ مرتين أو حرّر أثناء الحفظ، تحتاج قاعدة لِمَن تؤول الاستجابة. بعض الاحتياطات التي تعمل في أي نمط: تعطيل الحفظ أثناء الحفظ (أو تخفيض النقرات)، إرفاق requestId (أو إصدار) بكل حفظ وتجاهل الاستجابات القديمة، إلغاء العمل الجاري عند مغادرة المستخدم، وتحديد معنى التعديلات أثناء الحفظ (جدولة حفظ آخر، أو تعليم النموذج كقذر مرة أخرى).

النجاح الجزئي شائع أيضًا: الخادم يقبل بعض الحقول ويُرجع رفضًا لحقول أخرى. نمذج ذلك صراحة. احتفظ بأخطاء لكل حقل (وإن لزم، حالة مزامنة لكل حقل) حتى تستطيع إظهار "تم الحفظ" عمومًا مع إبراز الحقل الذي يحتاج انتباهًا.

حالات الخطأ التي يمكن للمستخدمين التعافي منها

Move from form to payment
If your form ends in payment, add Stripe logic as part of the same workflow.
Connect Stripe

شاشات النماذج تفشل بطرق أكثر من "حدث خطأ ما". إذا أصبح كل فشل مجرد رسالة عامة، يعيد المستخدم كتابة البيانات، ويفقد الثقة، ويتخلى عن العملية. الهدف دائمًا: الحفاظ على سلامة الإدخال، عرض إصلاح واضح، وجعل إعادة المحاولة طبيعية.

من المفيد فصل الأخطاء بحسب مكانها. صيغة بريد خاطئة ليست مثل انقطاع الخادم.

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

قاعدة أساسية للتعافي: لا تمسح مدخلات المستخدم عند الفشل. إذا فشل الحفظ، احتفظ بالقيم الحالية في الذاكرة وعلى القرص. إعادة المحاولة يجب أن تعيد إرسال نفس الحمولة ما لم يحرر المستخدم.

الاختلاف في الأنماط هو كيف تُسقَط أخطاء الخادم إلى حالة الواجهة. في MVVM، من السهل تحديث تدفقات متعددة أو حقول بالخطأ وخلق تناقضات. في MVI، عادةً تطبق استجابة الخادم في خطوة مخفّض واحدة تحدّث fieldErrors و formError معًا.

وحدد أيضًا ما هو حالة وما هو تأثير لمرة واحدة. الأخطاء المضمنة و"فشل الإرسال" تنتمي إلى الحالة (يجب أن تبقى بعد التدوير). الإجراءات لمرة واحدة مثل snackbar أو الاهتزاز تنتمي للتأثيرات.

المسودات دون اتصال واستعادة النماذج قيد العمل

تطبيق ذو نماذج كثيفة يشعر بأنه "دون اتصال" حتى عندما تكون الشبكة جيدة. المستخدمون يبدلون التطبيقات، النظام يقتل عمليتك، أو يفقدون الإشارة في منتصف العمل. المسودات تحميهم من البدء من جديد.

أولًا، عرّف ماذا تعني المسودة. حفظ النموذج النظيف فقط غالبًا لا يكفي. عادة تريد استعادة الشاشة كما بدت بالضبط، بما في ذلك الحقول نصف المكتوبة.

ما يستحق الحفظ هو أساسًا الإدخال الخام للمستخدم (النص كما كُتِب، المعرفات المختارة، URIs للمرفقات)، بالإضافة إلى ميتاداتا كافية للدمج بأمان لاحقًا: لقطة خادم معروفة وآخر مؤشر إصدار (updatedAt، ETag، أو عدّاد بسيط). يمكن إعادة حساب التحقق عند الاستعادة.

خيار التخزين يعتمد على الحساسية والحجم. المسودات الصغيرة يمكن أن تعيش في التفضيلات، لكن النماذج متعددة الخطوات والمرفقات أكثر أمانًا في قاعدة بيانات محلية. إذا احتوت المسودة بيانات شخصية، استخدم تخزينً مشفّرًا.

أكبر سؤال معماري هو أين يعيش مصدر الحقيقة. في MVVM، غالبًا ما يحفظ الفريق من ViewModel عند كل تغيير للحقل. في MVI، قد يكون الحفظ أسهل بعد كل تحديث من المخفّض لأنك تحفظ حالة متماسكة واحدة (أو كائن Draft مشتق).

توقيت الحفظ التلقائي مهم. الحفظ عند كل ضغطة يولد ضوضاء؛ تأخير قصير (مثلاً 300 إلى 800 ملّي ثانية) مع حفظ عند تغيير المرحلة يعمل جيدًا.

عند عودة المستخدم إلى الاتصال، تحتاج قواعد دمج. نهج عملي: إذا لم يتغير إصدار الخادم، طبق المسودة وأرسلها. إذا تغيّر، اعرض خيارًا واضحًا: احتفظ بمسودتي أو أعد تحميل بيانات الخادم.

خطوة بخطوة: تنفيذ نموذج موثوق بأي نمط

Handle sign-in without detours
Use built-in authentication modules so form screens can focus on the user task.
Add Auth

النماذج الموثوقة تبدأ بقواعد واضحة، لا بالكود فقط. كل إجراء من المستخدم يجب أن يؤدي إلى حالة متوقعة، وكل نتيجة غير متزامنة يجب أن تهبط في مكان واضح واحد.

اكتب الأفعال التي يجب على شاشتك الاستجابة لها: الكتابة، فقدان التركيز، الإرسال، إعادة المحاولة، والتنقل بين الخطوات. في MVVM تصبح هذه طرق ViewModel وتحديثات حالة. في MVI تصبح أحداثًا صريحة.

ثم ابنِ على مراحل صغيرة:

  1. عرّف الأحداث لدورة الحياة كاملة: تعديل، blur، submit، نجاح/فشل الحفظ، retry، استعادة المسودة.
  2. صمّم كائن حالة واحد: قيم الحقول، أخطاء الحقول، حالة النموذج العامة، و"هل هناك تغييرات غير محفوظة".
  3. أضف التحقق: فحوصات خفيفة أثناء التحرير، وفحوصات أثقل عند الإرسال.
  4. أضف قواعد الحفظ المتفائلة: ما يتغير فورًا، وما يسبب التراجع.
  5. أضف المسودات: حفظ تلقائي مع debounce، استعادة عند الفتح، وإظهار مؤشر صغير "تم استعادة مسودة" ليثق المستخدم بما يراه.

عامل الأخطاء كجزء من التجربة. احتفظ بالمدخلات، ظلل فقط ما يحتاج تصحيحًا، وقدم إجراءً واضحًا واحدًا بعدها (تعديل، إعادة المحاولة، أو الاحتفاظ بالمسودة).

إذا أردت برمجة حالة نماذج معقدة قبل كتابة واجهة Android، منصة دون كود مثل AppMaster (appmaster.io) قد تكون مفيدة للتحقق من سير العمل أولًا. بعدها يمكنك تنفيذ نفس القواعد في MVVM أو MVI مع مفاجآت أقل.

سيناريو توضيحي: نموذج تقرير مصاريف متعدد الخطوات

تخيل نموذج مصاريف من 4 خطوات: تفاصيل (التاريخ، الفئة، المبلغ)، رفع الإيصال، الملاحظات، ثم مراجعة وإرسال. بعد الإرسال، يعرض حالة الموافقة مثل Draft، Submitted، Rejected، Approved. الأجزاء الصعبة هي التحقق، الحفظات التي قد تفشل، والحفاظ على مسودة عند انقطاع الشبكة.

في MVVM، عادة تحتفظ بـ FormUiState في ViewModel (غالبًا StateFlow). كل تغيير حقل يستدعي دالة ViewModel مثل onAmountChanged() أو onReceiptSelected(). يعمل التحقق على التغيير، عند التنقل بين الخطوات، أو عند الإرسال. بنية شائعة هي المدخلات الخام بالإضافة لأخطاء الحقول، مع أعلام مشتقة تتحكم فيما إذا كان Next/Submit ممكنًا.

في MVI، يصبح نفس التدفق صريحًا: ترسل الواجهة نوايا مثل AmountChanged، NextClicked، SubmitClicked، و RetrySave. يعيد المخفّض حالة جديدة. الآثار الجانبية (رفع الإيصال، استدعاء API، إظهار snackbar) تعمل خارج المخفّض وتُعيد النتائج كأحداث.

عمليًا، يجعل MVVM إضافة وظائف وتحديث تدفق بسرعة سهلة. يجعل MVI من الصعب تجاوز انتقال حالة بطريق الخطأ لأن كل تغيير يمر عبر المخفّض.

أخطاء ف شائعة وفخاخ

Make state changes predictable
Turn your validation and save rules into clear Business Processes you can test early.
Start Building

معظم أخطاء النماذج تأتي من قواعد غير واضحة عن من يملك الحقيقة، ومتى يعمل التحقق، وماذا يحدث عندما تصل النتائج غير المتزامنة متأخرة.

الخطأ الأكثر شيوعًا هو مزج مصادر الحقيقة. إذا كان حقل نصي أحيانًا يقرأ من عنصر واجهة، أحيانًا من حالة ViewModel، وأحيانًا من مسودة مستعادة، ستحصل على إعادة ضبط عشوائية وتبليغ "اختفى مدخلي". اختر حالة رسمية للشاشة واستخرج كل شيء منها (نموذج الدومين، صفوف الكاش، حمولة API).

فخ سهل آخر هو الخلط بين الحالة والأحداث. snackbar، التنقل، أو لافتة "تم الحفظ!" هي أحداث لمرة واحدة. رسالة خطأ يجب أن تبقى مرئية حتى يعدل المستخدم هي حالة. الخلط يسبب تكرار التأثيرات عند التدوير أو فقدانها.

مشكلتان تتكرران كثيرًا:

  • الإفراط في التحقق على كل ضغطة مفتاح، خاصة للفحوص المكلفة. استخدم debounce، تحقق على blur، أو تحقق فقط للحقول الملموسة.
  • تجاهل النتائج غير المرتبة. إذا حفظ المستخدم مرتين أو عدّل بعد الحفظ، يمكن أن تكتب الاستجابات القديمة فوق الأحدث ما لم تستخدم معرفات طلب (أو منطق "الأحدث فقط").

أخيرًا، المسودات ليست "حفظ JSON فقط". بدون ترقيم إصدارات، قد تكسر تحديثات التطبيق عملية الاستعادة. أضف رقم إصدار مخطط وقصة ترحيل بسيطة، حتى لو كانت "احذف وابدأ من جديد" للإصدارات القديمة جدًا.

قائمة تحقق سريعة قبل الإطلاق

Ship the backend with it
Create API endpoints and business logic without hand-writing boilerplate for every form.
Generate Backend

قبل الجدل حول MVVM مقابل MVI، تأكد أن لديك مصدر واحد واضح للحقيقة. إذا قيمة يمكن أن تتغير على الشاشة، فهي تنتمي إلى الحالة، لا إلى ويدجت العرض أو علم مخفي.

فحص عملي قبل الإطلاق:

  • الحالة تشمل المدخلات، أخطاء الحقول، حالة الحفظ (idle/saving/saved/failed)، وحالة المسودات/الطوابير حتى لا تضطر الواجهة للتخمين.
  • قواعد التحقق نقية وقابلة للاختبار دون واجهة.
  • واجهة المستخدم المتفائلة لها مسار تراجع للحالات التي يرفضها الخادم.
  • الأخطاء لا تمسح مدخلات المستخدم.
  • استعادة المسودة متوقعة: إما شريط استعادة تلقائي واضح أو زر "استعادة المسودة" صريح.

اختبار واحد يكتشف أخطاء حقيقية: شغل وضع الطائرة في منتصف حفظ، أطفئه، ثم أعد المحاولة مرتين. المحاولة الثانية يجب ألا تُنشئ مُدخلًا مكررًا. استخدم معرف طلب، مفتاح قابل للمعاملة idempotency، أو علامة محلية "حفظ معلق" لجعل إعادة المحاولات آمنة.

إذا كانت إجاباتك غامضة، شدد نموذج الحالة أولًا، ثم اختر النمط الذي يجعل تلك القواعد أسهل في الفرض.

الخطوات التالية: اختيار مسار والبناء أسرع

ابدأ بسؤال واحد: ما تكلفة أن ينتهي نموذجك في حالة نصف محدثة؟ إذا كانت التكلفة منخفضة، اجعل الأمور بسيطة.

MVVM مناسب عندما تكون الشاشة مباشرة، الحالة هي غالبًا "حقول + أخطاء"، وفريقك يصدّر بثقة باستخدام ViewModel + LiveData/StateFlow.

MVI أفضل عندما تحتاج انتقالات حالة صارمة ومتوقعة، الكثير من الأحداث غير المتزامنة (الحفظ التلقائي، إعادة المحاولة، المزامنة)، أو عندما تكون أخطاء التطبيق باهظة التكلفة (المدفوعات، الامتثال، سير عمل حرج).

بأي مسار تختار، أعلى الاختبارات المُجدية للنماذج عادة لا تلمس الواجهة: حواف التحقق، انتقالات الحالة (تحرير، إرسال، نجاح، فشل، إعادة المحاولة)، تراجع الحفظ المتفائل، واستعادة المسودة وسلوك الصراع.

إذا احتجت أيضًا backend، شاشات إدارة، وAPIs مع تطبيقك المحمول، AppMaster (appmaster.io) يمكنه توليد backend وجاهز للإنتاج، واجهات ويب، وتطبيقات محمولة أصلية من نموذج واحد، مما يساعد في الحفاظ على قواعد التحقق وسير العمل متسقة عبر السطوح.

الأسئلة الشائعة

When should I choose MVVM vs MVI for a form-heavy Android screen?

اختر MVVM عندما يكون تدفق الاستمارة خطيًا في الغالب وفريقك لديه بالفعل اتفاقيات واضحة لاستخدام StateFlow/LiveData والتعامل مع الأحداث لمرة واحدة وإلغاء العمليات. اختر MVI عندما تتوقع الكثير من الأعمال غير المتزامنة المتداخلة (مثل الحفظ التلقائي، إعادة المحاولة، التحميلات) وتريد قواعد صارمة تمنع تغيّر الحالة من مصادر متعددة.

What’s the simplest way to keep form state from drifting out of sync?

ابدأ بكائن حالة واحد للشاشة (على سبيل المثال، FormState) يحتوي على قيم الحقول الخام، أخطاء كل حقل، خطأ على مستوى النموذج، وحالات واضحة مثل Saving أو Failed. احتفظ بالعلامات المشتقة مثل isValid و canSubmit محسوبة في مكان واحد حتى تقتصر واجهة المستخدم على العرض فقط ولا تعيد اتخاذ القرارات.

How often should validation run in a form: on every keystroke or only on submit?

نفذ فحوصات خفيفة وسريعة أثناء التحرير (مطلوب، نطاق، صيغة أساسية) وفحوصات صارمة عند الإرسال. اجعل منطق التحقق خارج واجهة المستخدم ليكون قابلاً للاختبار، واحفظ الأخطاء في الحالة حتى تبقى بعد تدوير الشاشة أو استعادة العملية.

How do I handle async validation like “email already taken” without stale results?

عامل التحقق غير المتزامن على أنه "آخر إدخال يفوز". خزّن القيمة التي تحققت منها (أو معرف الطلب/الإصدار) وتجاهل النتائج التي لا تتطابق مع الحالة الحالية. هذا يمنع استجابات قديمة من الكتابة فوق نص أحدث، وهو سبب شائع لرسائل الخطأ العشوائية.

What’s a safe default approach for optimistic UI when saving a form?

حدّث واجهة المستخدم فورًا لتعكس الإجراء (مثلاً عرض Saving… والإبقاء على المدخلات مرئية)، لكن احرص دائمًا على وجود آلية للتراجع إذا رفض الخادم الحفظ. استخدم معرف طلب/إصدار، عطّل أو قلل تكرار زر الحفظ، وعرّف ماذا يعني التعديل أثناء الحفظ (قفل الحقول، جدولة حفظ آخر، أو تعليم النموذج كمعدل).

How should I structure error states so users can recover without retyping?

لا تمسح مدخلات المستخدم عند الفشل. ضع مشكلات الحقول inline بجوار الحقل المعني، احتفظ بأخطاء مستوى النموذج بالقرب من زر الإرسال، واجعل أخطاء الشبكة قابلة للاسترداد عبر إعادة المحاولة التي تعيد إرسال نفس الحمولة ما لم يغيّر المستخدم شيئًا.

Where should one-time events like snackbars and navigation live?

أبعد التأثيرات لمرة واحدة عن الحالة المستمرة. في MVVM، أرسلها عبر تيار منفصل (مثل SharedFlow)؛ وفي MVI، نمذجها كـ Effects يستهلكها الواجهة مرة واحدة. هذا يتجنّب تكرار الرسائل أو التنقل بعد تدوير الشاشة.

What exactly should I save for offline drafts of a form?

احفظ في الأساس مدخلات المستخدم الخام (كما كُتبت)، ومع القليل من الميتاداتا اللازمة للدمج الآمن لاحقًا مثل مؤشر إصدار آخر للخادم. أعد حساب التحقق عند الاستعادة بدلاً من حفظه، وأضف رقم إصدار لمخطط التخزين حتى تستطيع التعامل مع تحديثات التطبيق دون كسر الاستعادة.

How should autosave be timed so it feels reliable but not noisy?

استخدم تأخير قصير (debounce) لبضعة مئات من الملّي ثانية وحفظ عند تغيير الخطوة أو عند إرسال التطبيق إلى الخلفية. الحفظ عند كل ضغطة مفتاح يسبب ضوضاء ومنافسة زائدة، بينما الحفظ عند الخروج فقط قد يفقد العمل إذا انتهت العملية.

How do I handle draft conflicts when the server data changed while the user was offline?

احفظ علامة إصدار (مثل updatedAt، ETag، أو عدّاد محلي) لكل من لقطة الخادم والمسودة. إذا لم يتغير إصدار الخادم، طبّق المسودة وأرسلها؛ إذا تغيّر، اعرض خيارًا واضحًا للاحتفاظ بالمسودة أو إعادة تحميل بيانات الخادم بدلاً من الكتابة الصامتة على أي طرف.

من السهل أن تبدأ
أنشئ شيئًا رائعًا

تجربة مع AppMaster مع خطة مجانية.
عندما تكون جاهزًا ، يمكنك اختيار الاشتراك المناسب.

البدء