Build resilient mobile apps with offline capabilities, data sync, and network handling.
# React Native Offline-First Apps
Build robust offline-first React Native applications with Google Antigravity IDE. This guide covers local storage, sync strategies, and conflict resolution for mobile apps that work without network connectivity.
## Why Offline-First?
Offline-first architecture ensures your app works seamlessly regardless of network conditions. Google Antigravity IDE's Gemini 3 engine provides intelligent patterns for data synchronization and conflict resolution.
## Local Storage with MMKV
```typescript
// lib/storage.ts
import { MMKV } from "react-native-mmkv";
export const storage = new MMKV({
id: "app-storage",
encryptionKey: process.env.STORAGE_KEY,
});
// Type-safe storage wrapper
export const localStore = {
get<T>(key: string): T | null {
const value = storage.getString(key);
return value ? JSON.parse(value) : null;
},
set<T>(key: string, value: T): void {
storage.set(key, JSON.stringify(value));
},
delete(key: string): void {
storage.delete(key);
},
clear(): void {
storage.clearAll();
},
};
```
## Offline Queue System
```typescript
// lib/offlineQueue.ts
interface QueuedAction {
id: string;
type: "create" | "update" | "delete";
endpoint: string;
payload: unknown;
timestamp: number;
retries: number;
}
class OfflineQueue {
private queue: QueuedAction[] = [];
private processing = false;
constructor() {
this.loadQueue();
}
private loadQueue() {
this.queue = localStore.get<QueuedAction[]>("offline_queue") || [];
}
private saveQueue() {
localStore.set("offline_queue", this.queue);
}
add(action: Omit<QueuedAction, "id" | "timestamp" | "retries">) {
const queuedAction: QueuedAction = {
...action,
id: crypto.randomUUID(),
timestamp: Date.now(),
retries: 0,
};
this.queue.push(queuedAction);
this.saveQueue();
this.processQueue();
}
async processQueue() {
if (this.processing || !navigator.onLine) return;
this.processing = true;
while (this.queue.length > 0) {
const action = this.queue[0];
try {
await this.executeAction(action);
this.queue.shift();
this.saveQueue();
} catch (error) {
action.retries++;
if (action.retries >= 3) {
this.queue.shift();
this.handleFailedAction(action);
}
this.saveQueue();
break;
}
}
this.processing = false;
}
private async executeAction(action: QueuedAction) {
const method = action.type === "delete" ? "DELETE" :
action.type === "create" ? "POST" : "PUT";
const response = await fetch(action.endpoint, {
method,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(action.payload),
});
if (!response.ok) throw new Error("Sync failed");
return response.json();
}
private handleFailedAction(action: QueuedAction) {
console.error("Action failed after retries:", action);
// Notify user or store for manual resolution
}
}
export const offlineQueue = new OfflineQueue();
```
## Sync Hook
```typescript
// hooks/useOfflineSync.ts
import { useEffect, useState } from "react";
import NetInfo from "@react-native-community/netinfo";
import { offlineQueue } from "../lib/offlineQueue";
export function useOfflineSync<T>(
key: string,
fetcher: () => Promise<T>,
dependencies: unknown[] = []
) {
const [data, setData] = useState<T | null>(
() => localStore.get<T>(key)
);
const [isOnline, setIsOnline] = useState(true);
const [syncing, setSyncing] = useState(false);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener((state) => {
setIsOnline(state.isConnected ?? false);
if (state.isConnected) {
offlineQueue.processQueue();
}
});
return unsubscribe;
}, []);
useEffect(() => {
async function sync() {
if (!isOnline) return;
setSyncing(true);
try {
const freshData = await fetcher();
setData(freshData);
localStore.set(key, freshData);
} catch (error) {
console.error("Sync failed:", error);
} finally {
setSyncing(false);
}
}
sync();
}, [isOnline, ...dependencies]);
return { data, isOnline, syncing };
}
```
## Conflict Resolution
```typescript
// lib/conflictResolver.ts
interface VersionedData {
id: string;
data: unknown;
version: number;
updatedAt: number;
}
export function resolveConflict(
local: VersionedData,
remote: VersionedData,
strategy: "local-wins" | "remote-wins" | "latest-wins" = "latest-wins"
): VersionedData {
switch (strategy) {
case "local-wins":
return { ...local, version: Math.max(local.version, remote.version) + 1 };
case "remote-wins":
return remote;
case "latest-wins":
return local.updatedAt > remote.updatedAt ? local : remote;
}
}
```
## Best Practices
- Use MMKV for fast synchronous storage
- Implement queue-based sync for reliability
- Handle conflicts with versioning strategies
- Monitor network state with NetInfo
- Provide clear offline indicators to users
- Test extensively in airplane mode
Google Antigravity IDE provides intelligent suggestions for offline-first patterns and automatically detects potential sync conflicts in your React Native applications.This React Native prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their react native implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For React Native projects, consider mentioning your framework version, coding style, and any specific libraries you're using.