MobileEngineeringMENA

تطبيقات الجوال التي تعمل بدون إنترنت أولاً بـ React Native وExpo

كيف تصمم تطبيقات React Native وExpo تبقي المستخدمين منتجين عند انقطاع الاتصال — وتزامن البيانات بأمان عند عودة الشبكة.

BahrTech Team
١٨ فبراير ٢٠٢٦ · 4 دقيقة قراءة

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

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

العمل دون اتصال متطلب منتج لا ميزة اختيارية

تصميم offline-first يبدأ بسؤال واحد: ماذا يجب أن يستطيع المستخدمون فعله دون شبكة؟ الإجابة تشكل البنية بأكملها.

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

لا شيء من هذه يحتاج رد خادم ليكون مفيداً. العمل يحدث محلياً ويتزامن عند عودة الاتصال. البديل — حجب المستخدمين على الاتصال — يدمر قيمة الأداة في البيئات التي يجب أن تكون فيها لا غنى عنها.

خزّن النية لا الحالة فقط

أهم كائن offline ليس شاشة مخزنة مؤقتاً. إنه نية المستخدم: أنشئ هذا الموعد، أشر لهذه الفاتورة كمدفوعة، ارفع هذا المستند. مثّل الأعمال المعلقة كصف دائم من العمليات قابلة للإعادة والفحص والعرض للمستخدم.

type PendingOperation =
  | {
      id: string; // معرف ثابت من العميل (cuid أو uuid)
      type: "appointment.create";
      payload: { patientId: string; startsAt: string; notes?: string };
      createdAt: string;
      retryCount: number;
      lastError?: string;
    }
  | {
      id: string;
      type: "invoice.markPaid";
      payload: { invoiceId: string; paidAt: string };
      createdAt: string;
      retryCount: number;
      lastError?: string;
    };

حفظ هذا الصف في التخزين الدائم للجهاز — لا في React state — يعني أن العمليات تنجو من إعادة تشغيل التطبيق والجهاز. المستخدم لا يفقد عمله لأنه أغلق التطبيق.

اختيار المكتبات: MMKV وSQLite وWatermelonDB

MMKV (react-native-mmkv) هو مخزن key-value سريع مدعوم بملفات memory-mapped. استخدمه للقيم الصغيرة كثيرة القراءة: تفضيلات المستخدم، رموز المصادقة، صف العمليات المعلقة عندما يكون صغيراً. يُسلسل بـ JSON مما يبسط الـ API.

SQLite عبر Expo SQLite هو الطبقة المناسبة للبيانات العلائقية offline — سجلات المرضى، تاريخ المواعيد، المخزون. يعمل SQLite المُدار من Expo بشكل موثوق على المنصتين دون خطوات بناء native، مما يهم في سير عمل Expo-managed.

WatermelonDB يبني على SQLite ويضيف استعلامات reactive وتحميل كسول وبروتوكول مزامنة مدمج. يستحق تكلفة الإعداد لتطبيقات ذات بيانات علائقية معقدة وكثير من المراقبين المتزامنين.

لمعظم تطبيقات العيادات والعمل الميداني التي نبنيها، SQLite للبيانات المنظمة وMMKV لصف العمليات يغطي كامل سطح offline.

اجعل كل عملية قابلة للتكرار بأمان

مزامنة offline تفشل عندما تنشئ إعادة المحاولة سجلات مكررة. الموعد المنشأ مرتين. الدفعة المسجلة مرتين. كل عملية كتابة تحتاج معرفاً ثابتاً من العميل يستخدمه الخادم كمفتاح idempotency:

// العميل ينشئ المعرف قبل المحاولة الأولى
const operationId = createId(); // cuid2

// تنفيذ الخادم
async function createAppointment(input: CreateInput, idempotencyKey: string) {
  const existing = await prisma.appointment.findUnique({
    where: { clientId: idempotencyKey },
  });
  if (existing) return existing; // آمن للإرجاع — تم الإنشاء بالفعل

  return prisma.appointment.create({
    data: { ...input, clientId: idempotencyKey },
  });
}

العميل يرسل نفس idempotencyKey في كل إعادة محاولة. الخادم يعيد دائماً نفس النتيجة. استدعاءات الشبكة المكررة تصبح غير ضارة.

حل التعارضات يحتاج قرارات منتج لا قرارات تقنية فقط

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

خيارات بتصاعد في التعقيد:

آخر كتابة تفوز حسب الحقل — كل حقل يتتبع updatedAt خاصة به. تغييرات حقول مختلفة تُدمج؛ تغييرات متزامنة على نفس الحقل تستخدم أحدث طابع زمني. مناسب لمعظم بيانات وصف الموعد.

تعيين العمليات أو CRDT — للتحرير التعاوني الآني. مبالغ فيه لمعظم تطبيقات العمل الميداني.

قائمة مراجعة يدوية — الكتابات المتعارضة تُعلَّم وتُحال للمشرف أو المستخدم. الخيار الصحيح للبيانات المالية والسجلات الطبية حيث الكتابة الصامتة غير مقبولة.

حدد سياسة التعارض لكل نوع سجل في مرحلة التصميم، لا بعد حادثة المزامنة الأولى في الإنتاج.

صمم الواجهة حول حالة عدم اليقين

عندما يُحفظ سجل محلياً ولم يتزامن بعد، يجب أن يعرف المستخدم. عند فشل المزامنة يجب أن يعرف ويجد مساراً واضحاً للتعافي. دوامات تحميل غامضة تختفي في النهاية هي أسوأ نتيجة — تتركهم يتساءلون هل حُفظ عملهم.

أنماط مفيدة:

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

المزامنة الخلفية مع Expo Task Manager

import * as BackgroundFetch from "expo-background-fetch";
import * as TaskManager from "expo-task-manager";

const SYNC_TASK = "background-sync";

TaskManager.defineTask(SYNC_TASK, async () => {
  const queue = await loadPendingOperations();
  if (queue.length === 0) return BackgroundFetch.BackgroundFetchResult.NoData;

  for (const op of queue) {
    await retryOperation(op);
  }
  return BackgroundFetch.BackgroundFetchResult.NewData;
});

await BackgroundFetch.registerTaskAsync(SYNC_TASK, {
  minimumInterval: 60,
  stopOnTerminate: false,
  startOnBoot: true,
});

مهام الخلفية على iOS لها قيود زمنية صارمة (~30 ثانية) وقد لا تعمل حسب الجدول. صمم المزامنة لتكون تدريجية — معالجة عدة عمليات لكل تشغيل خلفي — حتى تكون التقدمات الجزئية مفيدة.

تطبيقات الجوال offline-first تنجح عندما تحفظ نية المستخدم، وتعيد العمليات بأمان بمفاتيح idempotency، وتعرض حالة المزامنة بصدق، وتجعل حل التعارضات قراراً منتجياً لا فكرة لاحقة.

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

الوسوم
MobileEngineeringMENA