{"id":"l-rn-4-2","courseId":"react-native-apps","lang":"en","kind":"lesson","summary":"Choose between AsyncStorage, expo-sqlite and expo-secure-store for each kind of data in Trailhead, and use each one correctly, from preferences to a migrated hike database to the sign-in token.","html":"<p>A hiking app that forgets your log when you lose signal, or asks you to sign in every morning, gets deleted. Trailhead has three kinds of data to keep on the device, and each fits a different tool:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th>Data</th>\n<th>Example</th>\n<th>Tool</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Small preferences</td>\n<td>units (km or mi), last-used tab</td>\n<td>AsyncStorage</td>\n</tr>\n<tr>\n<td>Structured records you query</td>\n<td>the hike log, sorted and filtered</td>\n<td>expo-sqlite</td>\n</tr>\n<tr>\n<td>Secrets</td>\n<td>the sign-in refresh token</td>\n<td>expo-secure-store</td>\n</tr>\n</tbody>\n</table></div><p>Picking the wrong one rarely fails loudly. It fails as slow screens, lost data after an update, or a security review you don’t pass.</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 stores preferences in AsyncStorage as unencrypted strings, hikes in an SQLite database it can query, and the refresh token in SecureStore, encrypted by the Keychain or Keystore. Photos are files on disk whose paths are stored in 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\">strings, unencrypted</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\">tables, queries</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>Three stores, three jobs</figcaption></figure>\n<h2 id=\"sec-1\" tabindex=\"-1\">AsyncStorage for preferences</h2>\n<p>AsyncStorage is a persistent key-value store of <strong>strings</strong>. Every call is async, and nothing is encrypted:</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=\"Copy code\"><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>Copy</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>Store one JSON blob under a versioned key rather than many tiny keys, merge with defaults when reading (so a new preference added in v2 gets a sensible value for old users), and survive corrupt data with a <code dir=\"ltr\">try</code>. Install it with <code dir=\"ltr\">npx expo install @react-native-async-storage/async-storage</code>. If your app already uses expo-sqlite, <code dir=\"ltr\">expo-sqlite/kv-store</code> offers the same API backed by SQLite, so you can skip the extra dependency.</p>\n<p>Read preferences once at startup, keep them in a context like the hikes, and write them back when they change. Calling <code dir=\"ltr\">getItem</code> inside components on every render turns a fast screen into one that flickers through default values first.</p>\n<p>AsyncStorage gets slow and awkward when you start storing lists of records in it: every read parses the whole blob, and there’s no way to ask for “hikes over 10 km in 2026” without loading everything.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">expo-sqlite for the hike log</h2>\n<p>The hike log is structured data you sort, filter and total, which is what a database is for. expo-sqlite gives you a real SQLite database file on the device. <code dir=\"ltr\">SQLiteProvider</code> opens it and runs your setup before any child renders:</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=\"Copy code\"><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>Copy</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> is a number SQLite stores in the file for you. Each <code dir=\"ltr\">if</code> block upgrades one step, so a user on version 0 runs both, and a user on version 1 runs only the second. Never edit an old step once it has shipped; add a new one.</p>\n<p>Any screen can then use the database:</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=\"Copy code\"><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>Copy</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>In practice you’d call these from your reducer’s provider: load the hikes once into state, and write to SQLite whenever an action changes them.</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\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Building SQL with template strings</span></p>\n<p><code dir=\"ltr\">`INSERT INTO hikes (name) VALUES ('${name}')`</code> breaks on the first hike called “O’Brien Ridge” and lets crafted input rewrite your query. The <code dir=\"ltr\">?</code> placeholders bind values safely, whatever they contain. Make it a rule: no user data ever goes into the SQL text itself.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">SecureStore for secrets</h2>\n<p>The refresh token lets Trailhead sign in silently. Anyone who reads it can act as the user, so it goes in expo-secure-store, which encrypts values using the iOS Keychain and the Android Keystore:</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=\"Copy code\"><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>Copy</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>This is what the <code dir=\"ltr\">isLoading</code> state in the previous section’s <code dir=\"ltr\">useSession</code> was waiting for: at startup, <code dir=\"ltr\">readToken()</code> decides whether the user lands on the tabs or on the sign-in screen. SecureStore is meant for <strong>small</strong> values; some platform versions have rejected values over about 2 KB. Keep tokens and keys there, never whole objects or files. It doesn’t run on the web, so a web build needs a different approach.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Files belong on disk</h2>\n<p>A trail photo is several megabytes. Don’t put it in SQLite or AsyncStorage. Keep the file on disk (expo-file-system manages your app’s document directory) and store only its URI in the <code dir=\"ltr\">photoUri</code> column. Your database stays small and fast, and the image component loads the file directly.</p>\n<p>All of this data survives app updates, including over-the-air ones, and is deleted when the user uninstalls the app, with one quirk: on iOS, Keychain values from SecureStore can survive a reinstall, so don’t treat a missing database as proof that no token exists. If losing it would hurt, sync it to your server, which is exactly why Trailhead has accounts.</p>\n<p>Next, the most delicate part of talking to the device: asking for permission to use its location and camera.</p>\n","toc":[{"id":"sec-1","text":"AsyncStorage for preferences","level":2},{"id":"sec-2","text":"expo-sqlite for the hike log","level":2},{"id":"sec-3","text":"SecureStore for secrets","level":2},{"id":"sec-4","text":"Files belong on disk","level":2}],"takeaways":["AsyncStorage is an unencrypted, asynchronous string key-value store, right for small preferences and wrong for secrets.","expo-sqlite stores structured data you query, sort and filter; run schema migrations in <code dir=\"ltr\">onInit</code> using <code dir=\"ltr\">PRAGMA user_version</code>.","Always pass SQL values as parameters (<code dir=\"ltr\">?</code>), never by building the SQL string yourself.","expo-secure-store encrypts small values with the platform’s Keychain or Keystore; tokens and keys belong there.","Store files such as photos on the file system and keep only their paths in the database."],"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>Where should Trailhead keep the refresh token it receives after sign-in?</p>","options":[{"text":"AsyncStorage, under a key like <code dir=\"ltr\">auth.refreshToken</code>.","why":"AsyncStorage is unencrypted. On a compromised or backed-up device, the token can be read as plain text."},{"text":"An <code dir=\"ltr\">EXPO_PUBLIC_</code> environment variable.","why":"Those are baked into the app bundle at build time; they can’t hold per-user values and are readable by anyone."},{"text":"expo-secure-store, which encrypts it with the Keychain on iOS and the Keystore on Android.","why":"Correct. Small secrets like tokens are exactly what it’s for."},{"text":"A <code dir=\"ltr\">tokens</code> table in the SQLite database.","why":"The database file isn’t encrypted by default, so the token would sit in plain text next to the hikes."}],"answer":2},{"q":"<p>Which line is safe for saving a hike’s name typed by the user?</p>","options":[{"text":"<code dir=\"ltr\">db.runAsync(`INSERT INTO hikes (name) VALUES ('${name}')`)</code>","why":"A name containing a quote, like O’Brien Ridge, breaks the statement, and crafted input can change the SQL itself."},{"text":"<code dir=\"ltr\">db.runAsync('INSERT INTO hikes (name) VALUES (?)', name)</code>","why":"Correct. The value is bound as a parameter, so it’s always treated as data, never as SQL."},{"text":"<code dir=\"ltr\">db.execAsync('INSERT INTO hikes (name) VALUES (' + JSON.stringify(name) + ')')</code>","why":"JSON quoting isn’t SQL quoting, and <code dir=\"ltr\">execAsync</code> doesn’t bind parameters. It’s string-building with extra steps."}],"answer":1},{"q":"<p>Trailhead v2 adds an <code dir=\"ltr\">elevationM</code> column to the hikes table. Existing users already have v1’s table. What’s the reliable approach?</p>","options":[{"text":"In the <code dir=\"ltr\">onInit</code> migration, read <code dir=\"ltr\">PRAGMA user_version</code>; if it’s 1, run <code dir=\"ltr\">ALTER TABLE hikes ADD COLUMN elevationM INTEGER</code> and set the version to 2.","why":"Correct. Versioned migrations upgrade each user’s database exactly once, in order, whatever version they start from."},{"text":"Delete the database at startup and recreate it with the new schema.","why":"That wipes every user’s hike log on update. Data on the device is the user’s; you can’t reload it from anywhere."},{"text":"Change the <code dir=\"ltr\">CREATE TABLE</code> statement; SQLite updates existing tables to match.","why":"<code dir=\"ltr\">CREATE TABLE IF NOT EXISTS</code> skips existing tables entirely, so old users never get the new column."}],"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":"Put each piece of data in the right store","prompt":"<p>A teammate drafted Trailhead’s storage plan. Select <strong>every</strong> line of the plan that you would approve in code review.</p>\n","hints":["Ask two questions about each piece of data: is it secret, and will you need to query or filter it?","SecureStore is for small secrets; photos are megabytes.","Three lines are right."],"explanation":"<p>Preferences are small and harmless, so AsyncStorage (one JSON blob under a versioned key) fits. The hike log is structured data you sort and filter, so it belongs in expo-sqlite with versioned migrations. The refresh token is a secret, so it belongs in expo-secure-store, encrypted by the Keychain or Keystore. Tokens in AsyncStorage sit in plain text; photos in SecureStore exceed the size it’s designed for; and a 600-hike log in AsyncStorage means parsing everything for every query.</p>\n","checkLabels":{},"optionText":{"units-async":{"text":"Store the user’s units preference (km or mi) and reminder setting as one JSON value in AsyncStorage under <code dir=\"ltr\">trailhead.prefs.v1</code>.","why":"Approve. Small, non-secret preferences are what AsyncStorage is for, and the versioned key makes future changes easy."},"log-sqlite":{"text":"Store the hike log in an expo-sqlite table, with schema changes handled by migrations keyed on <code dir=\"ltr\">PRAGMA user_version</code>.","why":"Approve. Structured records you query belong in a database, and versioned migrations keep old users’ data intact."},"token-async":{"text":"Store the refresh token in AsyncStorage so it loads fast at startup.","why":"Reject. AsyncStorage is unencrypted. Startup speed doesn’t justify keeping a credential in plain text."},"photo-secure":{"text":"Store each trail photo as a base64 string in expo-secure-store so photos are private.","why":"Reject. SecureStore is designed for small values; multi-megabyte photos belong on the file system, with only their paths in SQLite."},"token-secure":{"text":"Store the refresh token in expo-secure-store and delete it on sign-out.","why":"Approve. SecureStore encrypts it with the platform keystore, and deleting it on sign-out leaves nothing behind."},"log-async":{"text":"Keep all 600 hikes as one JSON array in AsyncStorage and filter it in JavaScript.","why":"Reject. Every read parses the whole log, every write rewrites it, and there’s no way to query without loading everything."}}},"words":676,"readMinutes":3}