# Storage

# Overview

Storage Option Type Performance Security Best For Offline Sync Complexity
AsyncStorage Key-Value ⚡ Low ❌ No Small data, flags, prefs ❌ No Easy
MMKV Key-Value ⚡⚡⚡ Very High ✅ Optional Fast key-value storage ❌ No Easy
SecureStore / Keychain Key-Value (Secure) ⚡ Medium ✅ Encrypted Tokens, passwords ❌ No Easy
SQLite Relational DB ⚡⚡ Medium ❌ Not by default Structured data, queries ❌ No Medium
Realm Object DB ⚡⚡ High ✅ Encrypted option Complex data models, offline apps ✅ Yes Medium
WatermelonDB ORM on SQLite ⚡⚡ High ❌ Not built-in Large offline-first apps ✅ Yes Hard

👉 Quick Recommendations:

  • Store user prefs, tokens → AsyncStorage or SecureStore.
  • Need fast storage with encryption → MMKV.
  • Need relational data (tables, queries) → SQLite.
  • Need complex offline-first app with sync → Realm or WatermelonDB.

Recommended for Access Tokens

Storage Option Security Notes
SecureStore (Expo) ✅ Encrypted Uses iOS Keychain & Android Keystore. Easiest if you’re in Expo ecosystem.
react-native-keychain ✅ Encrypted Community package, works in bare RN. Stores tokens in Keychain/Keystore.
MMKV (Encrypted mode) ⚠️ Medium MMKV supports encryption, but it’s still app-managed. Safer than plain AsyncStorage, but weaker than OS-managed keystore.
AsyncStorage ❌ Insecure Data is stored in plaintext on device, not safe for tokens.

# MMKV + React Query for Offline data

gamesCacheManager.ts

import { MMKV } from 'react-native-mmkv'

const storage = new MMKV()

export const gamesCacheManager = {
  getCachedItem: (locale: string) => {
    const value = storage.getString(`games_${locale}`)
    return value ? JSON.parse(value) : null
  },

  storeItem: (locale: string, games: any[]) => {
    storage.set(`games_${locale}`, JSON.stringify(games))
  },

  clearItem: (locale: string) => {
    storage.delete(`games_${locale}`)
  }
}

useAllGameQuery.ts

export function useAllGameQuery() {
  const { i18n } = useLingui()

  return useSuspenseQuery({
    queryKey: ['games', i18n.locale],
    initialData: () => {
      // Load from MMKV first
      return gamesCacheManager.getCachedItem(i18n.locale) || undefined
    },
    queryFn: async () => {
      const games = await getGames()
    gamesCacheManager.storeItem(i18n.locale, games)
      return games
    },
  })
}

Benefits of This Setup

  • Cold start = instant data → MMKV gives you the previous snapshot.
  • React Query still does refetching → so the cache stays fresh.
  • Minimal complexity → just JSON stringify/parse around MMKV.
  • Locale-aware → since you’re keying by i18n.locale.