Skip to content
Go back

Handling Offline Support in Expo Apps

Handling Offline Support in Expo Apps

Introduction

Offline support ensures your app remains functional without network connectivity. This guide covers caching, local storage, and data synchronization in Expo.

Prerequisites

Step 1: Install Dependencies

expo install expo-sqlite @react-native-async-storage/async-storage @nozbe/watermelondb

Step 2: Choose Storage Strategy

Step 3: Setup AsyncStorage for Caching

Create utils/cache.js:

import AsyncStorage from "@react-native-async-storage/async-storage";

export async function setCache(key, value) {
  try {
    await AsyncStorage.setItem(key, JSON.stringify(value));
  } catch (e) {
    console.error("Failed to save cache:", e);
  }
}

export async function getCache(key) {
  try {
    const json = await AsyncStorage.getItem(key);
    return json != null ? JSON.parse(json) : null;
  } catch (e) {
    console.error("Failed to load cache:", e);
    return null;
  }
}

Step 4: Use SQLite for Structured Data

Create utils/db.js:

import * as SQLite from "expo-sqlite";

const db = SQLite.openDatabase("app.db");

export function initDatabase() {
  db.transaction(tx => {
    tx.executeSql(
      `CREATE TABLE IF NOT EXISTS items (
        id TEXT PRIMARY KEY NOT NULL,
        data TEXT NOT NULL
      );`
    );
  });
}

export function insertItem(id, data) {
  db.transaction(tx => {
    tx.executeSql("INSERT OR REPLACE INTO items (id, data) VALUES (?, ?);", [
      id,
      JSON.stringify(data),
    ]);
  });
}

export function getItems(callback) {
  db.transaction(tx => {
    tx.executeSql("SELECT * FROM items;", [], (_, { rows }) =>
      callback(rows._array)
    );
  });
}

Step 5: Setup WatermelonDB for Sync

Initialize in db/schema.js:

import { appSchema, tableSchema } from "@nozbe/watermelondb";

export const mySchema = appSchema({
  version: 1,
  tables: [
    tableSchema({
      name: "tasks",
      columns: [
        { name: "title", type: "string" },
        { name: "completed", type: "boolean" },
      ],
    }),
  ],
});

In db/index.js:

import { Database } from "@nozbe/watermelondb";
import SQLiteAdapter from "@nozbe/watermelondb/adapters/sqlite";
import { mySchema } from "./schema";
import Task from "./models/Task";

const adapter = new SQLiteAdapter({ schema: mySchema });

export const database = new Database({
  adapter,
  modelClasses: [Task],
});

Step 6: Implement Sync Logic

In sync.js:

import { database } from "./db";
import { synchronize } from "@nozbe/watermelondb/sync";

export async function sync() {
  try {
    await synchronize({
      database,
      pullChanges: async ({ lastPulledAt }) => {
        const res = await fetch(
          `https://api.example.com/sync?lastPulledAt=${lastPulledAt}`
        );
        return res.json();
      },
      pushChanges: async ({ changes }) => {
        await fetch("https://api.example.com/sync", {
          method: "POST",
          body: JSON.stringify(changes),
        });
      },
    });
  } catch (error) {
    console.error("Sync failed:", error);
  }
}

Step 7: Offline-First Pattern Example

In component:

import React, { useEffect, useState } from "react";
import { View, Text, FlatList } from "react-native";
import { getCache, setCache } from "@/utils/cache";
import { getItems } from "@/utils/db";

export default function OfflineFirstList() {
  const [items, setItemsState] = useState([]);

  useEffect(() => {
    // Load from local storage
    getCache("items").then(cached => {
      if (cached) setItemsState(cached);
    });

    // Fetch from network
    fetch("https://api.example.com/items")
      .then(res => res.json())
      .then(async data => {
        setItemsState(data);
        await setCache("items", data);
      })
      .catch(() => console.log("Network fetch failed, using cache"));
  }, []);

  return (
    <FlatList
      data={items}
      keyExtractor={item => item.id}
      renderItem={({ item }) => <Text>{item.name}</Text>}
    />
  );
}

Summary

Offline support in Expo can be achieved using AsyncStorage, SQLite, or WatermelonDB with sync. Implement caching and sync strategies for seamless user experiences.


Share this post on:

Previous Post
Connecting Expo Apps to AWS Amplify Backend
Next Post
Using Expo + Reanimated for Smooth UI Animations