{"id":"l-rn-4-2","courseId":"react-native-apps","lang":"ar","kind":"lesson","summary":"اختر بين AsyncStorage وexpo-sqlite وexpo-secure-store لكل نوع من البيانات في Trailhead، واستخدم كلًّا منها بشكل صحيح، من التفضيلات إلى قاعدة بيانات الرحلات مع الـ migrations إلى token تسجيل الدخول.","html":"<p>تطبيق مشي ينسى سجلّك حين تفقد الإشارة، أو يطلب منك تسجيل الدخول كل صباح، مصيره الحذف. لدى Trailhead ثلاثة أنواع من البيانات يجب حفظها على الجهاز، ولكلٍّ منها أداة مختلفة:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>البيانات</th>\n<th>مثال</th>\n<th>الأداة</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>تفضيلات صغيرة</td>\n<td>الوحدات (km أو mi)، آخر tab مستخدم</td>\n<td>AsyncStorage</td>\n</tr>\n<tr>\n<td>سجلّات منظّمة تستعلم عنها</td>\n<td>سجلّ الرحلات، مرتّبًا ومصفّى</td>\n<td>expo-sqlite</td>\n</tr>\n<tr>\n<td>أسرار</td>\n<td>الـ refresh token لتسجيل الدخول</td>\n<td>expo-secure-store</td>\n</tr>\n</tbody>\n</table></div><p>اختيار الأداة الخاطئة نادرًا ما يفشل بصوت عالٍ. بل يفشل في صورة شاشات بطيئة، أو بيانات ضائعة بعد تحديث، أو مراجعة أمنية لا تجتازها.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 230\" role=\"img\" aria-labelledby=\"l-rn-4-2-t1\"><defs><marker id=\"arrow-l-rn-4-2-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-rn-4-2-t1\">يخزّن Trailhead التفضيلات في AsyncStorage نصوصًا غير مشفّرة، والرحلات في قاعدة بيانات SQLite يستطيع الاستعلام عنها، والـ refresh token في SecureStore مشفّرًا عبر Keychain أو Keystore. والصور ملفات على القرص تُخزَّن مساراتها في SQLite.</title>\n  <rect class=\"d-box-primary\" x=\"250\" y=\"16\" width=\"180\" height=\"48\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"340\" y=\"46\" text-anchor=\"middle\">Trailhead</text>\n  <rect class=\"d-box\" x=\"20\" y=\"120\" width=\"190\" height=\"90\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"115\" y=\"148\" text-anchor=\"middle\">AsyncStorage</text>\n  <text class=\"d-label-muted\" x=\"115\" y=\"172\" text-anchor=\"middle\">نصوص، غير مشفّرة</text>\n  <text class=\"d-code\" x=\"115\" y=\"196\" text-anchor=\"middle\">units = \"km\"</text>\n  <rect class=\"d-box-accent\" x=\"245\" y=\"120\" width=\"190\" height=\"90\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"340\" y=\"148\" text-anchor=\"middle\">expo-sqlite</text>\n  <text class=\"d-label-muted\" x=\"340\" y=\"172\" text-anchor=\"middle\">جداول، استعلامات</text>\n  <text class=\"d-code\" x=\"340\" y=\"196\" text-anchor=\"middle\">hikes, photo paths</text>\n  <rect class=\"d-box-success\" x=\"470\" y=\"120\" width=\"190\" height=\"90\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"565\" y=\"148\" text-anchor=\"middle\">SecureStore</text>\n  <text class=\"d-label-muted\" x=\"565\" y=\"172\" text-anchor=\"middle\">Keychain / Keystore</text>\n  <text class=\"d-code\" x=\"565\" y=\"196\" text-anchor=\"middle\">refresh token</text>\n  <path class=\"d-arrow\" d=\"M290 64 L130 118\" marker-end=\"url(#arrow-l-rn-4-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M340 64 L340 118\" marker-end=\"url(#arrow-l-rn-4-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M390 64 L550 118\" marker-end=\"url(#arrow-l-rn-4-2-1)\"/>\n</svg><figcaption>ثلاثة مخازن، وثلاث وظائف</figcaption></figure>\n<h2 id=\"sec-1\" tabindex=\"-1\">AsyncStorage للتفضيلات</h2>\n<p>AsyncStorage مخزن مفتاح-قيمة دائم لـ <strong>النصوص</strong>. كل استدعاء فيه غير متزامن، ولا شيء مشفّر:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">src/lib/prefs.ts</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> AsyncStorage </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '@react-native-async-storage/async-storage'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Prefs</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">units</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'km'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'mi'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">remindersOn</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> boolean</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> KEY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'trailhead.prefs.v1'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> defaults</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Prefs</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { units: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'km'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, remindersOn: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">false</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> loadPrefs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Prefs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> raw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> AsyncStorage.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">getItem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">KEY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">raw) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> defaults;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  try</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">defaults, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">JSON</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">parse</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(raw) };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">catch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> defaults;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> savePrefs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">prefs</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Prefs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> AsyncStorage.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">setItem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">KEY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">JSON</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">stringify</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(prefs));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>خزّن قيمة JSON واحدة تحت مفتاح مرقّم بإصدار بدلًا من مفاتيح صغيرة كثيرة، وادمجها مع القيم الافتراضية عند القراءة (حتى يحصل تفضيل جديد أُضيف في الإصدار الثاني على قيمة معقولة لدى المستخدمين القدامى)، واصمد أمام البيانات التالفة باستخدام <code dir=\"ltr\">try</code>. ثبّته بالأمر <code dir=\"ltr\">npx expo install @react-native-async-storage/async-storage</code>. وإن كان تطبيقك يستخدم expo-sqlite أصلًا، فإن <code dir=\"ltr\">expo-sqlite/kv-store</code> يقدّم الواجهة البرمجية نفسها مدعومة بـ SQLite، فتستغني عن الاعتمادية الإضافية.</p>\n<p>اقرأ التفضيلات مرة واحدة عند بدء التشغيل، واحفظها في context مثل الرحلات، واكتبها مجددًا حين تتغيّر. استدعاء <code dir=\"ltr\">getItem</code> داخل الـ components في كل render يحوّل شاشة سريعة إلى شاشة تومض بالقيم الافتراضية أولًا.</p>\n<p>ويصبح AsyncStorage بطيئًا ومربكًا حين تبدأ بتخزين قوائم من السجلّات فيه: كل قراءة تحلّل الكتلة كلها، ولا طريقة لطلب «الرحلات التي تتجاوز 10 km في 2026» دون تحميل كل شيء.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">expo-sqlite لسجلّ الرحلات</h2>\n<p>سجلّ الرحلات بيانات منظّمة ترتّبها وتصفّيها وتجمعها، وهذا ما وُجدت قاعدة البيانات من أجله. يعطيك expo-sqlite ملف قاعدة بيانات SQLite حقيقيًا على الجهاز. يفتحه <code dir=\"ltr\">SQLiteProvider</code> ويشغّل الإعداد الخاص بك قبل أن يُرسم أي ابن:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">src/app/_layout.tsx</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { Stack } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'expo-router'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { SQLiteProvider, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> SQLiteDatabase } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'expo-sqlite'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> migrate</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">db</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> SQLiteDatabase</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> row</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> db.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">getFirstAsync</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;{ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">user_version</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }>(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'PRAGMA user_version'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> version </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> row?.user_version </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (version </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> db.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">execAsync</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">      PRAGMA journal_mode = WAL;</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">      CREATE TABLE hikes (</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">        id TEXT PRIMARY KEY NOT NULL,</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">        name TEXT NOT NULL,</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">        date TEXT NOT NULL,</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">        distanceKm REAL NOT NULL,</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">        favorite INTEGER NOT NULL DEFAULT 0</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">      );</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    `</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    version </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (version </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> db.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">execAsync</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'ALTER TABLE hikes ADD COLUMN photoUri TEXT'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    version </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> db.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">execAsync</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`PRAGMA user_version = ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">version</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> RootLayout</span><span style=\"color:#953800;--shiki-dark:#FFA657\">() </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">SQLiteProvider</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> databaseName</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"trailhead.db\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onInit</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">migrate</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Stack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">SQLiteProvider</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p><code dir=\"ltr\">PRAGMA user_version</code> رقم يخزّنه SQLite في الملف نيابةً عنك. كل كتلة <code dir=\"ltr\">if</code> ترقّي خطوة واحدة، فالمستخدم على الإصدار 0 يشغّل الاثنتين، والمستخدم على الإصدار 1 يشغّل الثانية فقط. لا تعدّل أبدًا خطوة قديمة بعد إطلاقها؛ بل أضف خطوة جديدة.</p>\n<p>بعد ذلك تستطيع أي شاشة استخدام قاعدة البيانات:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">TSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { useSQLiteContext } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'expo-sqlite'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> db</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useSQLiteContext</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// in a component or custom hook</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// …later, inside an async function such as an effect or a save handler:</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> hikes</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> db.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">getAllAsync</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Hike</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'SELECT * FROM hikes ORDER BY date DESC'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> db.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">runAsync</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  'INSERT INTO hikes (id, name, date, distanceKm) VALUES (?, ?, ?, ?)'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  hike.id, hike.name, hike.date, hike.distanceKm,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span></code></pre></div>\n<p>عمليًا ستستدعي هذه من الـ provider الخاص بالـ reducer: حمّل الرحلات مرة واحدة إلى الـ state، واكتب إلى SQLite كلما غيّرها action.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>بناء SQL باستخدام template strings</span></p>\n<p><code dir=\"ltr\">`INSERT INTO hikes (name) VALUES ('${name}')`</code> ينكسر مع أول رحلة اسمها “O’Brien Ridge”، ويسمح لمدخلات مصمّمة بخبث بإعادة كتابة استعلامك. أما العلامات <code dir=\"ltr\">?</code> فتربط القيم بأمان، أيًّا كان محتواها. اجعلها قاعدة: لا بيانات من المستخدم تدخل نص الـ SQL نفسه أبدًا.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">SecureStore للأسرار</h2>\n<p>يتيح الـ refresh token لـ Trailhead تسجيل الدخول بصمت. ومن يقرؤه يستطيع التصرّف بوصفه المستخدم، لذا يوضع في expo-secure-store، الذي يشفّر القيم باستخدام Keychain في iOS وKeystore في Android:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">src/lib/session.ts</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> *</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> SecureStore </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'expo-secure-store'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> TOKEN_KEY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'trailhead.refreshToken'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> saveToken</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">token</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> SecureStore.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">setItemAsync</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">TOKEN_KEY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, token);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> readToken</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> SecureStore.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">getItemAsync</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">TOKEN_KEY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> clearToken</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> SecureStore.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">deleteItemAsync</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">TOKEN_KEY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span></code></pre></div>\n<p>هذا ما كانت حالة <code dir=\"ltr\">isLoading</code> في <code dir=\"ltr\">useSession</code> من القسم السابق تنتظره: عند بدء التشغيل، يقرّر <code dir=\"ltr\">readToken()</code> هل يصل المستخدم إلى الـ tabs أم إلى شاشة تسجيل الدخول. صُمّم SecureStore للقيم <strong>الصغيرة</strong>؛ وقد رفضت بعض إصدارات المنصّات قيمًا تتجاوز نحو 2 KB. احفظ فيه الـ tokens والمفاتيح، ولا تحفظ فيه كائنات كاملة أو ملفات أبدًا. وهو لا يعمل على الويب، فتحتاج نسخة الويب إلى نهج مختلف.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">مكان الملفات على القرص</h2>\n<p>صورة الدرب عدّة ميغابايتات. لا تضعها في SQLite ولا في AsyncStorage. احتفظ بالملف على القرص (يدير expo-file-system مجلّد المستندات الخاص بتطبيقك)، وخزّن الـ URI الخاص به فقط في عمود <code dir=\"ltr\">photoUri</code>. تبقى قاعدة بياناتك صغيرة وسريعة، ويحمّل component الصورة الملف مباشرة.</p>\n<p>كل هذه البيانات تصمد أمام تحديثات التطبيق، بما فيها التحديثات عبر الهواء، وتُحذف حين يُزيل المستخدم التطبيق، مع استثناء غريب: على iOS قد تبقى قيم الـ Keychain المحفوظة عبر SecureStore بعد إعادة التثبيت، فلا تعتبر غياب قاعدة البيانات دليلًا على عدم وجود token. وإن كان فقدانها مؤلمًا، فزامنها مع خادمك، وهذا بالضبط سبب وجود الحسابات في Trailhead.</p>\n<p>التالي: أدقّ جزء في التعامل مع الجهاز، أي طلب الإذن لاستخدام موقعه وكاميرته.</p>\n","toc":[{"id":"sec-1","text":"AsyncStorage للتفضيلات","level":2},{"id":"sec-2","text":"expo-sqlite لسجلّ الرحلات","level":2},{"id":"sec-3","text":"SecureStore للأسرار","level":2},{"id":"sec-4","text":"مكان الملفات على القرص","level":2}],"takeaways":["AsyncStorage مخزن مفتاح-قيمة للنصوص، غير مشفّر وغير متزامن، مناسب للتفضيلات الصغيرة وغير مناسب للأسرار.","يخزّن expo-sqlite البيانات المنظّمة التي تستعلم عنها وترتّبها وتصفّيها؛ شغّل الـ migrations الخاصة بالمخطّط (schema) داخل <code dir=\"ltr\">onInit</code> مستخدمًا <code dir=\"ltr\">PRAGMA user_version</code>.","مرّر قيم SQL دائمًا بوصفها معاملات (<code dir=\"ltr\">?</code>)، ولا تبنِ نص SQL بنفسك أبدًا.","يشفّر expo-secure-store القيم الصغيرة باستخدام Keychain أو Keystore الخاص بالمنصّة؛ ومكان الـ tokens والمفاتيح هناك.","خزّن الملفات مثل الصور في نظام الملفات، واحتفظ بمساراتها فقط في قاعدة البيانات."],"further":[{"title":"Store data","url":"https://docs.expo.dev/develop/user-interface/store-data/"},{"title":"SQLite","url":"https://docs.expo.dev/versions/latest/sdk/sqlite/"},{"title":"SecureStore","url":"https://docs.expo.dev/versions/latest/sdk/securestore/"}],"quiz":[{"q":"<p>أين يجب أن يحتفظ Trailhead بالـ refresh token الذي يتلقّاه بعد تسجيل الدخول؟</p>","options":[{"text":"في AsyncStorage، تحت مفتاح مثل <code dir=\"ltr\">auth.refreshToken</code>.","why":"AsyncStorage غير مشفّر. وعلى جهاز مخترق أو له نسخة احتياطية، يمكن قراءة الـ token نصًّا عاديًا."},{"text":"في متغيّر بيئة يبدأ بـ <code dir=\"ltr\">EXPO_PUBLIC_</code>.","why":"هذه تُدمج في حزمة التطبيق وقت البناء؛ فلا تستطيع حمل قيم خاصة بكل مستخدم، ويستطيع أي أحد قراءتها."},{"text":"في expo-secure-store، الذي يشفّره باستخدام Keychain على iOS وKeystore على Android.","why":"صحيح. الأسرار الصغيرة مثل الـ tokens هي بالضبط ما صُمّم له."},{"text":"في جدول <code dir=\"ltr\">tokens</code> داخل قاعدة بيانات SQLite.","why":"ملف قاعدة البيانات غير مشفّر افتراضيًا، فسيقبع الـ token نصًّا عاديًا بجوار الرحلات."}],"answer":2},{"q":"<p>أيّ سطر آمن لحفظ اسم رحلة كتبه المستخدم؟</p>","options":[{"text":"<code dir=\"ltr\">db.runAsync(`INSERT INTO hikes (name) VALUES ('${name}')`)</code>","why":"اسم يحتوي على علامة اقتباس، مثل O’Brien Ridge، يكسر العبارة، ويمكن لمدخلات مصمّمة بخبث أن تغيّر الـ SQL نفسه."},{"text":"<code dir=\"ltr\">db.runAsync('INSERT INTO hikes (name) VALUES (?)', name)</code>","why":"صحيح. تُربط القيمة بوصفها معاملًا، فتُعامَل دائمًا كبيانات، لا كـ SQL أبدًا."},{"text":"<code dir=\"ltr\">db.execAsync('INSERT INTO hikes (name) VALUES (' + JSON.stringify(name) + ')')</code>","why":"اقتباس JSON ليس اقتباس SQL، و<code dir=\"ltr\">execAsync</code> لا يربط المعاملات. إنه بناء للنصوص بخطوات إضافية."}],"answer":1},{"q":"<p>يضيف الإصدار الثاني من Trailhead عمود <code dir=\"ltr\">elevationM</code> إلى جدول الرحلات. المستخدمون الحاليون لديهم جدول الإصدار الأول أصلًا. ما الطريقة الموثوقة؟</p>","options":[{"text":"في migration داخل <code dir=\"ltr\">onInit</code>، اقرأ <code dir=\"ltr\">PRAGMA user_version</code>؛ وإن كانت 1، فشغّل <code dir=\"ltr\">ALTER TABLE hikes ADD COLUMN elevationM INTEGER</code> واضبط الإصدار على 2.","why":"صحيح. الـ migrations المرقّمة بالإصدارات ترقّي قاعدة بيانات كل مستخدم مرة واحدة بالضبط، بالترتيب، أيًّا كان الإصدار الذي يبدأ منه."},{"text":"احذف قاعدة البيانات عند بدء التشغيل وأعد إنشاءها بالمخطّط الجديد.","why":"هذا يمسح سجلّ رحلات كل مستخدم عند التحديث. البيانات على الجهاز ملك المستخدم؛ ولا يمكنك إعادة تحميلها من أي مكان."},{"text":"غيّر عبارة <code dir=\"ltr\">CREATE TABLE</code>؛ وسيحدّث SQLite الجداول الموجودة لتطابقها.","why":"<code dir=\"ltr\">CREATE TABLE IF NOT EXISTS</code> يتخطّى الجداول الموجودة كليًا، فلا يحصل المستخدمون القدامى على العمود الجديد أبدًا."}],"answer":0}],"exercise":{"kind":"choice","multiple":true,"options":["units-async","log-sqlite","token-async","photo-secure","token-secure","log-async"],"correct":["units-async","log-sqlite","token-secure"],"title":"ضع كل قطعة بيانات في المخزن الصحيح","prompt":"<p>كتب زميلك مسوّدة خطّة التخزين في Trailhead. اختر <strong>كل</strong> سطر في الخطّة كنت ستوافق عليه في مراجعة الكود.</p>\n","hints":["اطرح سؤالين عن كل قطعة بيانات: هل هي سرّية، وهل ستحتاج إلى الاستعلام عنها أو تصفيتها؟","SecureStore للأسرار الصغيرة؛ والصور بالميغابايتات.","ثلاثة أسطر صحيحة."],"explanation":"<p>التفضيلات صغيرة وغير ضارّة، فيناسبها AsyncStorage (قيمة JSON واحدة تحت مفتاح مرقّم بإصدار). وسجلّ الرحلات بيانات منظّمة ترتّبها وتصفّيها، فمكانه expo-sqlite مع migrations مرقّمة بالإصدارات. والـ refresh token سرّ، فمكانه expo-secure-store، مشفّرًا عبر Keychain أو Keystore. الـ tokens في AsyncStorage تقبع نصًّا عاديًا؛ والصور في SecureStore تتجاوز الحجم الذي صُمّم له؛ وسجلّ من 600 رحلة في AsyncStorage يعني تحليل كل شيء مع كل استعلام.</p>\n","checkLabels":{},"optionText":{"units-async":{"text":"خزّن تفضيل الوحدات لدى المستخدم (km أو mi) وإعداد التذكيرات قيمةَ JSON واحدة في AsyncStorage تحت <code dir=\"ltr\">trailhead.prefs.v1</code>.","why":"موافقة. التفضيلات الصغيرة غير السرّية هي ما وُجد AsyncStorage من أجله، والمفتاح المرقّم بإصدار يسهّل التغييرات المستقبلية."},"log-sqlite":{"text":"خزّن سجلّ الرحلات في جدول expo-sqlite، مع معالجة تغييرات المخطّط عبر migrations مبنية على <code dir=\"ltr\">PRAGMA user_version</code>.","why":"موافقة. السجلّات المنظّمة التي تستعلم عنها مكانها قاعدة بيانات، والـ migrations المرقّمة تحافظ على بيانات المستخدمين القدامى سليمة."},"token-async":{"text":"خزّن الـ refresh token في AsyncStorage ليُحمَّل بسرعة عند بدء التشغيل.","why":"رفض. AsyncStorage غير مشفّر. وسرعة بدء التشغيل لا تبرّر حفظ بيانات اعتماد نصًّا عاديًا."},"photo-secure":{"text":"خزّن كل صورة درب نصًّا بترميز base64 في expo-secure-store لتبقى الصور خاصة.","why":"رفض. صُمّم SecureStore للقيم الصغيرة؛ والصور ذات الميغابايتات مكانها نظام الملفات، مع حفظ مساراتها فقط في SQLite."},"token-secure":{"text":"خزّن الـ refresh token في expo-secure-store واحذفه عند تسجيل الخروج.","why":"موافقة. يشفّره SecureStore بمخزن المفاتيح الخاص بالمنصّة، وحذفه عند تسجيل الخروج لا يترك شيئًا خلفه."},"log-async":{"text":"احتفظ بالرحلات الـ 600 كلها مصفوفة JSON واحدة في AsyncStorage وصفّها في JavaScript.","why":"رفض. كل قراءة تحلّل السجلّ كله، وكل كتابة تعيد كتابته، ولا طريقة للاستعلام دون تحميل كل شيء."}}},"words":601,"readMinutes":4}