تعلم كيف تصمم وتبني وتختبر تطبيق قوائم تحقق يعمل بدون إنترنت: التخزين المحلي، المزامنة، حل التعارضات، الأمان، ونصائح النشر.

قبل اختيار قواعد البيانات أو تكتيكات المزامنة، كن محددًا بشأن من سيعتمد على قوائم التحقق غير المتصلة — وماذا يعني "غير متصل" بالنسبة لهم فعليًا. تطبيق يستخدمه مُنظِّم منزلي يختلف تمامًا عن تطبيق يستخدمه مفتشون في أقبية، مصانع، أو مواقع ريفية.
ابدأ بتسمية المستخدمين الأساسيين وبيئات عملهم:
لكل مجموعة، دوّن قيود الأجهزة (أجهزة مشتركة مقابل شخصية)، طول الجلسة النموذجي، ومدى تكرار اتصالهم بالإنترنت.
اكتب الإجراءات الأساسية التي يجب أن يكملها المستخدمون دون التفكير في الاتصال:
كما اذكر الإجراءات "التي يفضل توفرها" والتي يمكن تأجيلها (مثل البحث في السجل العام أو تصدير التقارير).
كن صريحًا بشأن ما يجب أن يعمل بالكامل دون اتصال (إنشاء تشغيل قائمة جديدة، حفظ التقدم فورًا، إرفاق صور) مقابل ما يمكن تأجيله (رفع الوسائط، المزامنة مع الزملاء، تعديلات الإدارة).
إذا كنت تعمل ضمن قواعد امتثال، حدّد المتطلبات مبكرًا: الطوابع الزمنية الموثوقة، هوية المستخدم، سجل نشاط غير قابل للتغيير، وقواعد حول التعديلات بعد الإرسال. تؤثر هذه القرارات على نموذج بياناتك وكيف تصمم المزامنة لاحقًا.
نجاح تطبيق قوائم التحقق غير المتصل يعتمد على قرار مبكر واحد: offline-first أم online-first مع استرداد عند عدم الاتصال.
Offline-first يعني أن التطبيق يعتبر الجهاز هو المكان الأساسي لعمل المستخدم. الشبكة ميزة ثانوية: المزامنة مهمة في الخلفية وليست شرطًا لاستخدام التطبيق.
Online-first مع استرداد يعني أن الخادم هو مصدر الحقيقة في معظم الأوقات، والتطبيق "يتابع العمل بصعوبة" أثناء انقطاع الشبكة (غالبًا قراءة فقط أو بتعديلات محدودة).
بالنسبة لقوائم التحقق المستخدمة في مواقع العمل، المستودعات، الرحلات، والأقبية، غالبًا ما يكون offline-first الأنسب لأنه يتجنب رسائل "عذراً، حاول لاحقًا" المحرجة عندما يحتاج العامل إلى وضع علامة الآن.
كن واضحًا بشأن قواعد القراءة/الكتابة. خط أساس عملي لـoffline-first:
عندما تقيد شيئًا ما أثناء عدم الاتصال (مثل دعوة أعضاء جدد)، أشر لذلك في واجهة المستخدم وفسر السبب.
Offline-first لا تزال تحتاج وعدًا: عملك سيتزامن عند عودة الاتصال. قرّر وابلغ المستخدمين:
قوائم المستخدم الفردي أبسط: التعارضات نادرة ويمكن حلها تلقائيًا غالبًا.
الفرق والقوائم المشتركة تتطلب قواعد أشد: قد يحرر شخصان نفس البند دون اتصال. اختر مبكرًا ما إذا كنت ستدعم تعاونًا في الوقت الحقيقي لاحقًا، وصمّم من الآن لـالمزامنة عبر أجهزة متعددة، سجل تدقيق، ومؤشرات "آخر تحديث بواسطة" لخفض المفاجآت.
تطبيق قوائم تحقق غير متصل جيد هو في الأساس مشكلة بيانات. إذا كان نموذجك نظيفًا ومتوقعًا، تصبح التعديلات غير المتصلة، إعادة المحاولة، والمزامنة أسهل بكثير.
ابدأ بتقسيم القائمة التي يملؤها شخص ما عن القائمة التي يصممها شخص ما.
هذا يتيح لك تحديث القوالب دون كسر الإدخالات التاريخية.
عامل كل سؤال/مهمة كبند item بمعرف ثابت. خزّن مدخلات المستخدم في answers مرتبطة بتشغيل + بند.
حقول عملية لتضمينها:
id: UUID ثابت (يُولَد على جانب العميل بحيث يوجد بدون اتصال)template_version: لمعرفة تعريف القالب الذي بُدئ منه التشغيلupdated_at: طابع زمني لآخر تعديل (لكل سجل)version (أو revision): عدد صحيح ترفعه عند كل تغيير محليتلك التلميحات "من غيّر ماذا ومتى" هي أساس منطق المزامنة لاحقًا.
العمل غير المتصل يتعرقل كثيرًا. أضف حقولًا مثل status (draft, in_progress, submitted)، started_at، وlast_opened_at. بالنسبة للإجابات، اسمح بالقيم الفارغة وحالة "صحة" خفيفة حتى يتمكن المستخدمون من حفظ مسودة حتى لو لم تُستكمل البنود المطلوبة بعد.
يجب الرجوع إلى الصور والملفات، لا تخزينها كبلاوبات فى جداول القوائم الرئيسية.
أنشئ جدول attachments مع:\n
answer_id (أو run_id) رابط\n- حالة الرفع (pending, uploading, uploaded, failed)\n
هذا يحافظ على قراءات القوائم سريعة ويسهّل إعادة محاولة رفع الملفات.قوائم التحقق غير المتصلة تحيا أو تموت حسب مخزن البيانات المحلي. تحتاج شيءًا سريعًا، قابلًا للبحث، وقابلًا للترقية — لأن مخططك سيتغير فورًا عندما يطلب المستخدمون الحقيقيون "حقل إضافي واحد فقط".
صمّم لشاشات القوائم الشائعة. فهرس الحقول التي تصفِّي بها أكثر:
قليل من الفهارس المختارة جيدًا غالبًا أفضل من فهرسة كل شيء (التي تبطئ الكتابات وتزيد التخزين).
قم بترقيم مخططك من الإصدار الأول. يجب أن يتضمن كل تغيير:\n
priority الجديد استنادًا إلى القوالب)اختبر الهجرات ببيانات شبه حقيقية، ليس بقواعد بيانات فارغة.
تتنامى قواعد البيانات المحلية بصمت. خطط مبكرًا لـ:
هذا يبقي التطبيق سريعًا حتى بعد أشهر من الاستخدام في الميدان.
تطبيق قوائم غير متصل جيد لا "يزامِن الشاشات" — بل يزامِن إجراءات المستخدم. أبسط طريقة لذلك هي صندوق الصادر (outbox): كل تغيير يجريه المستخدم يُسجَّل محليًا أولًا، ثم يُرسل إلى الخادم لاحقًا.
عندما يضع المستخدم علامة على بند، يضيف ملاحظة، أو يكمل قائمة، اكتب ذلك الإجراء في جدول محلي مثل outbox_events مع:\n
event_id فريد (UUID)\n- type (مثل CHECK_ITEM, ADD_NOTE)\n- payload (التفاصيل)\n- created_at\n- status (pending, sending, sent, failed)هذا يجعل العمل دون اتصال فوريًا ومتوقعًا: الواجهة تحدث نفسها من قاعدة البيانات المحلية، بينما يعمل نظام المزامنة في الخلفية.
لا يجب أن تعمل المزامنة باستمرار. اختر محفزات واضحة حتى يحصل المستخدمون على تحديثات في الوقت المناسب دون استنزاف البطارية:
حافظ على القواعد بسيطة ومرئية. إذا تعذر على التطبيق المزامنة، أظهر مؤشر حالة صغيرًا واجعل العمل قابلاً للاستخدام.
بدلًا من إرسال طلب HTTP لكل خانة، جمّع عدة أحداث من الـoutbox في طلب واحد (مثلاً 20–100 حدث). التجميع يقلل من تشغيل راديو الهاتف، يحسّن الإنتاجية على الشبكات الهشة، ويُقصر زمن المزامنة.
الشبكات تسقط الطلبات. يجب أن تفترض المزامنة أن كل طلب قد يُرسل مرتين.\n
اجعل كل حدث قابلًا لإعادة التطبيق عبر تضمين event_id وجعل الخادم يخزن المعرفات المعالجة (أو استخدام مفتاح idempotency). إذا وصل نفس الحدث مرة أخرى، يرجع الخادم نجاحًا دون تطبيقه مرتين. هذا يسمح بإعادة المحاولة المكثفة مع تراجع للزمن دون إنشاء عناصر مكررة أو إكمال مهام مرتين.
إذا رغبت في تحسين إشارات تجربة المستخدم حول المزامنة، اربط ذلك مع قسم سير العمل غير المتصل في الأعلى.
قوائم التحقق غير المتصلة تبدو بسيطة حتى يُحرّر نفس العنصر على جهازين (أو يُحرّر دون اتصال في جهاز بينما يُحرّر عبر الإنترنت في آخر). إذا لم تخطط للتعارضات مُسبقًا، ستحصل على عناصر "مفقودة" بطريقة غامضة، مهام مكررة، أو ملاحظات تم الكتابة فوقها — وهي مشكلات موثوقية لا تحتملها تطبيقات القوائم.
أنماط تظهر مرارًا:\n
اختر استراتيجية واحدة وكن صريحًا عن أين تُطبق:\n
تجمع معظم التطبيقات بين هذه: دمج حقل-بحقل افتراضيًا، LWW لبعض الحقول، وحل بمساعدة المستخدم للحالات الأخرى.
التعارضات ليست شيئًا "تلاحظه لاحقًا" — تحتاج إشارات مبنية في بياناتك:\n
عند المزامنة، إذا تغيّرت مراجعة الخادم منذ المراجعة الأساسية المحلية، فذلك يعني وجود تعارض يجب حله.
عندما يتطلب الإدخال قرار المستخدم، اجعلها سريعة:\n
التخطيط لهذا مبكرًا يوافق بين منطق المزامنة، مخطط التخزين، وتجربة المستخدم ويمنع مفاجآت مزعجة قبل الإطلاق.
الدعم دون اتصال يشعر بأنه "حقيقي" فقط عندما تجعل الواجهة واضحة ما الذي يحدث. الأشخاص في المستودعات أو المستشفيات أو مواقع العمل لا يريدون التخمين إن كان عملهم آمنًا.
أظهر مؤشر حالة صغيرًا ومتماسكًا بالقرب من أعلى الشاشات الرئيسية:\n
عندما يفقد التطبيق الاتصال، تجنب النوافذ المنبثقة التي تعيق العمل. لافتة خفيفة قابلة للإغلاق عادةً كافية. عند عودة الاتصال، اعرض حالتي "جارٍ المزامنة..." لبضعة لحظات ثم امحها بهدوء.
يجب أن يشعر كل تعديل بأنه محفوظ فورًا حتى عند الانفصال. نمط جيد هو حالة حفظ من ثلاث مراحل:\n
ضع هذه التغذية بالقرب من العمل: بجانب عنوان القائمة، على مستوى صف البند (للقيم الحيوية)، أو في ملخص تذييل صغير ("3 تغييرات قيد المزامنة"). إذا فشل شيء في المزامنة، اعرض إجراء إعادة محاولة واضحة — لا تجبر المستخدمين على البحث عنه.
العمل دون اتصال يزيد تكلفة الأخطاء. أضف حواجز حماية:\n
فكّر أيضًا في عرض "استعادة المحذوفات مؤخرًا" لفترة قصيرة.
غالبًا ما تُملأ القوائم أثناء حمل أدوات أو ارتداء قفازات. روِّج للسرعة:\n
صمّم لطريق السلوك السعيد: يجب أن يكون بمقدور المستخدم إكمال قائمة بسرعة بينما يتعامل التطبيق بهدوء مع تفاصيل عدم الاتصال في الخلفية.
تفشل القوائم غير المتصلة إذا لم يتمكن المستخدم من الوصول إلى السياق اللازم لإكمالها — قوالب المهام، قوائم المعدات، معلومات الموقع، قواعد السلامة، أو قوائم منسدلة. اعتَبِر هذه "بيانات مرجعية" وخزنها محليًا جنبًا إلى جنب مع القائمة نفسها.
ابدأ بالحد الأدنى اللازم لإنهاء العمل دون تخمين:\n
قاعدة جيدة: إذا كانت الواجهة ستعرض مؤشر تحميل عند فتح قائمة متصلة، خزّن هذا الاعتماد.
ليس كل شيء يحتاج إلى نفس مستوى الحداثة. عرّف TTL لكل نوع بيانات:\n
أضف أيضًا محركات تحديث حدثية: عند تغيير المستخدم للموقع/المشروع، أو استلام مهمة جديدة، أو عند فتح قالب لم يتم التحقق منه مؤخرًا.
إذا تم تحديث قالب بينما شخص ما يعمل في قائمة، تجنّب تغيير النموذج بصمت. أعرض لافتة "تم تحديث القالب" مع خيارات:\n
إذا ظهرت حقول إلزامية جديدة، اعتبر القائمة "تحتاج تحديثًا قبل الإرسال" بدلًا من حظر الإكمال دون اتصال.
استخدم الترقيم والدلتا: مزامنة القوالب/صفوف البحث المتغيرة فقط (بواسطة updatedAt أو رموز تغيير الخادم). خزّن مؤشرات مزامنة لكل مجموعة بيانات حتى يتمكن التطبيق من الاستئناف بسرعة وتقليل النطاق الترددي — أمر مهم على الشبكات الخلوية.
قوائم التحقق غير المتصلة مفيدة لأن البيانات تعيش على الجهاز — حتى عند عدم وجود شبكة. هذا يعني أيضًا أنك مسؤول عن حمايتها إذا فُقد الهاتف، كان جهازًا مشتركًا، أو خُترق.
قرر مما تحميه ضد:
هذا يساعدك في اختيار مستوى الأمان المناسب دون إبطاء التطبيق بلا داع.
لا تخزن رموز الوصول نصًا عاديًا في التخزين المحلي. استخدم التخزين الآمن المقدم من النظام:\n
اجعل قاعدة البيانات المحلية خالية من الأسرار طويلة الأمد. إذا احتجت مفتاح تشفير لقاعدة البيانات، خزّنه في Keychain/Keystore.
يمكن أن يكون تشفير قاعدة البيانات فكرة جيدة للقوائم التي تتضمن بيانات شخصية، عناوين، صور، أو ملاحظات امتثال. التنازلات عادة:\n
إذا كان الخطر الأساسي هو "شخص يتصفح ملفات التطبيق"، فالتشفير ذو قيمة. إذا كانت البيانات منخفضة الحساسية والأجهزة تستخدم تشفير كامل للنظام، قد لا تحتاجه.
خطط لما يحدث إذا انتهت صلاحية الجلسة أثناء عدم الاتصال:\n
خزن الصور/الملفات في مسارات خاصة بالتطبيق، لا في مكتبات المشاركة. اربط كل مرفق بمستخدم مسجل دخولًا، نفّذ فحوص وصول داخل التطبيق، وامسح الملفات المخبأة عند تسجيل الخروج (واختياريًا عبر إجراء "إزالة البيانات غير المتصلة" في الإعدادات).
ميزة المزامنة التي تعمل على شبكة مكتبك قد تفشل في المصعد، المناطق الريفية، أو عندما يحد النظام من الأعمال الخلفية. اعتبر "الشَبَكة" غير موثوقة افتراضًا وصمّم المزامنة لتفشل بأمان وتتعافى سريعًا.
جعل كل نداء شبكة محدودًا زمنياً. طلب يتجمد لمدة دقيقتين يشعر وكأن التطبيق مجمَّد ويمكن أن يحجب أعمالًا أخرى.
استخدم الإعادة للخطأ العابر (مهلات، 502/503، مشاكل DNS مؤقتة)، لكن لا تقصف الخادم بالطلبات. طبق تراجعًا أسيًا (مثلاً 1s, 2s, 4s, 8s...) مع بعض التذبذب العشوائي حتى لا تُعيد آلاف الأجهزة المحاولة في نفس اللحظة بعد انقطاع.
عندما تسمح المنصة، شغّل المزامنة في الخلفية حتى تُرفع القوائم بهدوء عند عودة الاتصال. ومع ذلك، وفر إجراءً مرئيًا يدويًا مثل "مزامنة الآن" للطمأنة ولحالات تأخير المزامنة الخلفية.
اقترن ذلك بحالة واضحة: "آخر مزامنة قبل 12 دقيقة"، "3 عناصر قيد الانتظار"، ولافتة غير مقلقة عند عدم الاتصال.
التطبيقات غير المتصلة كثيرة الإعادة غالبًا ما تعيد نفس الإجراء عدة مرات. عيّن معرف طلب فريدًا لكل تغيير في الطابور (مثل event_id) وأرسله مع الطلب. على الخادم، خزّن المعرفات المعالجة وتجاهل التكرارات. هذا يمنع المستخدمين من إنشاء فحصين متماثلين أو توقيعين مزدوجين عند إعادة المحاولة.
خزّن أخطاء المزامنة مع السياق: أي قائمة، أي خطوة، وماذا يمكن للمستخدم عمله بعد ذلك. فضّل رسائل مثل "تعذر رفع صورتين — الاتصال بطيء جدًا. أبقِ التطبيق مفتوحًا واضغط مزامنة الآن." بدلًا من "فشل المزامنة." أضف خيارًا خفيفًا "نسخ التفاصيل" للدعم الفني.
ميزات عدم الاتصال عادة ما تفشل على الحواف: نفق، إشارة ضعيفة، حفظ نصف مكتمل، أو قائمة ضخمة تستغرق وقتًا كافيًا لانقطاع. خطة اختبار مركزة تكتشف تلك المشاكل قبل المستخدمين.
اختبر وضع الطيران على أجهزة فعلية، وليس فقط المحاكيات. ثم ازدد: غيّر الاتصال أثناء الإجراء.
جرّب سيناريوهات مثل:\n
أنت تتحقق أن الكتابات متينة محليًا، حالات الواجهة تبقى متسقة، والتطبيق لا «ينسى» التغييرات المعلقة.
طابور المزامنة منطق أعمال، فعاملّه كذلك. أضف اختبارات آلية تغطي:\n
مجموعة صغيرة من الاختبارات الحتمية هنا تمنع فئة الأخطاء الأغلى: تلف البيانات الصامت.
انشئ مجموعات بيانات كبيرة وواقعية: قوائم طويلة، الكثير من العناصر المكتملة، ومرفقات. قِس:\n
اختبر أيضًا الأجهزة ذات الأداء الأسوأ (أندرويد منخفض المواصفات، آيفونات أقدم) حيث تكشف عمليات الإدخال/الإخراج البطيئة عن اختناقات.
أضف تحليلات لتتبع معدل نجاح المزامنة وزمن الوصول إلى المزامنة (من التغيير المحلي إلى حالة مؤكدّة على الخادم). راقب القمم بعد الإصدارات وقطّع حسب نوع الشبكة. هذا يحوّل "المزامنة تبدو متقلبة" إلى أرقام واضحة قابلة للعمل.
إطلاق تطبيق قوائم غير متصل ليس حدثًا لمرة واحدة — بل بداية حلقة تغذية راجعة. الهدف هو الإصدار الآمن، مراقبة الاستخدام الحقيقي، وتحسين المزامنة وجودة البيانات من دون مفاجآت للمستخدمين.
قبل الطرح، ثبّت النقاط النهائية التي يعتمد عليها التطبيق حتى تتطور العميل والخادم بتوقعات واضحة:\n
حافظ على استجابات متسقة وصريحة (ما قُبل، ما رُفض، ما يُعاد) حتى يتمكن التطبيق من التعافي برفق.
قضايا عدم الاتصال غالبًا ما تكون غير مرئية إلا إذا قستها. تتبع:\n
نبه على القمم وليس الأخطاء الفردية، ودوّن معرفات الارتباط حتى يتمكن الدعم من تتبع قصة مزامنة مستخدم واحد.
استخدم أعلام الميزة لإصدار تغييرات المزامنة تدريجيًا ولتعطيل مسار معطّل بسرعة. اقترن ذلك بإجراءات هجرة مخطط محمية:\n
أضف إرشادًا خفيفًا: كيف تتعرف على حالة عدم الاتصال، ماذا يعني "قيد الانتظار"، ومتى ستتم المزامنة. انشر مقال مساعدة واربطه من داخل التطبيق (انظر أفكار في /blog/).
إذا أردت التحقق من هذه الأنماط بسرعة (مخزن محلي، صندوق صادر، وخادم بسيط Go/PostgreSQL)، منصة تطوير مثل Koder.ai يمكن أن تساعدك على بناء نموذج أولي وظيفي من مواصفات مدفوعة بالدردشة. يمكنك تكرار واجهة المستخدم وقواعد المزامنة، تصدير الشيفرة المصدرية عند الاستعداد، والاستمرار في تحسين الموثوقية بناءً على ملاحظات الميدان الحقيقية.
"غير متصل" يمكن أن يعني أي شيء من انقطاع قصير للاتصال إلى أيام من عدم التوفر. عرّف:
اختر offline-first إذا كان على المستخدمين إكمال قوائم التحقق بثقة في ظروف استقبال ضعيف/بدون استقبال: الهاتف هو بيئة العمل الأساسية والمزامنة تعمل في الخلفية.
اختر online-first مع وضع احتياطي فقط إذا كانت معظم الأعمال تتم عبر الإنترنت ويمكن أن يكون الوضع غير المتصل محدودًا (غالبًا قراءة فقط أو تعديلات بسيطة).
معيار عملي للوظائف التي يجب أن تعمل بدون اتصال:
قسّم بياناتك إلى:
هذا يمنع تحديثات القالب من كسر الإدخالات التاريخية ويسهل التدقيق.
استخدم معرفات UUID يولدها العميل حتى توجد السجلات دون اتصال، ثم أضف:
updated_at لكل سجلversion/revision تزدهر عند كل تغيير محليtemplate_version على التشغيلاتتجعل هذه الحقول المزامنة وإعادة المحاولة والكشف عن التعارضات أكثر قابلية للتنبؤ.
استخدم صندوق الصادر المحلي (outbox) الذي يسجل الإجراءات (ليس "مزامنة هذه الشاشة"). يجب أن يتضمن كل حدث:
اجعل كل تغيير آمنًا لإعادة المحاولة عبر إرسال event_id (مفتاح idempotency). يخزن الخادم المعرفات المعالجة ويتجاهل التكرارات.
هذا يمنع إنشاء تشغيلات مكررة أو تطبيق تبديلات الصناديق مرتين أو تكرار المرفقات عند انقطاع الشبكة أو إعادة الإرسال.
تجمع معظم التطبيقات استراتيجيات:
لاكتشاف التعارضات، تتبع مراجعة الخادم/ETag و لدى العميل عند بدء التحرير.
فضلًا استخدم مخزن بيانات يمكن الاستعلام منه:
أضف هجرات من اليوم الأول كي لا تكسر التغييرات التطبيقات المثبتة.
ابدأ بالافتراضيات الآمنة للنظام:
إذا انتهت الجلسة أثناء عدم الاتصال، اسمح بوصول محدود أو طابور تعديلات واطلب إعادة تسجيل للدخول قبل المزامنة.
إذا كان شيء مقيدًا (مثل دعوة أعضاء الفريق)، اشرحه في واجهة المستخدم.
event_id (UUID)type (مثل CHECK_ITEM, ADD_NOTE)payloadcreated_atstatus (pending, sending, sent, failed)تُحدَّث واجهة المستخدم من قاعدة البيانات المحلية فورًا؛ ويتولى الـoutbox المزامنة لاحقًا.