Claude-skills mobile-offline-support
Offline-first mobile apps with local storage, sync queues, conflict resolution. Use for offline functionality, data sync, connectivity handling, or encountering sync conflicts, queue management, storage limits, network transition errors.
install
source · Clone the upstream repo
git clone https://github.com/secondsky/claude-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/secondsky/claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/mobile-offline-support/skills/mobile-offline-support" ~/.claude/skills/secondsky-claude-skills-mobile-offline-support && rm -rf "$T"
manifest:
plugins/mobile-offline-support/skills/mobile-offline-support/SKILL.mdsource content
Mobile Offline Support
Build offline-first mobile applications with local storage and synchronization.
React Native Implementation
import AsyncStorage from '@react-native-async-storage/async-storage'; import NetInfo from '@react-native-community/netinfo'; class OfflineManager { constructor() { this.syncQueue = []; this.isOnline = true; // Maximum items in sync queue before discarding oldest this.MAX_SYNC_QUEUE_LENGTH = 1000; NetInfo.addEventListener(state => { this.isOnline = state.isConnected; if (this.isOnline) this.processQueue(); }); } /** * Fetch data from server. * TODO: Replace with actual API endpoint implementation. */ async fetchFromServer(key) { try { // Example implementation - replace with your API const response = await fetch(`${API_BASE_URL}/data/${key}`); if (!response.ok) { throw new Error(`Server returned ${response.status}`); } return await response.json(); } catch (error) { console.error('fetchFromServer failed:', error); throw new Error(`Failed to fetch ${key}: ${error.message}`); } } /** * Sync data to server. * TODO: Replace with actual API endpoint implementation. */ async syncToServer(key, data) { try { // Example implementation - replace with your API const response = await fetch(`${API_BASE_URL}/data/${key}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!response.ok) { throw new Error(`Server returned ${response.status}`); } return await response.json(); } catch (error) { console.error('syncToServer failed:', error); throw new Error(`Failed to sync ${key}: ${error.message}`); } } async getData(key) { const cached = await AsyncStorage.getItem(key); if (cached) return JSON.parse(cached); if (this.isOnline) { const data = await this.fetchFromServer(key); await AsyncStorage.setItem(key, JSON.stringify(data)); return data; } return null; } async saveData(key, data) { await AsyncStorage.setItem(key, JSON.stringify(data)); if (this.isOnline) { await this.syncToServer(key, data); } else { // Add to queue this.syncQueue.push({ key, data, timestamp: Date.now() }); // Enforce queue bounds - discard oldest if exceeded while (this.syncQueue.length > this.MAX_SYNC_QUEUE_LENGTH) { const discarded = this.syncQueue.shift(); console.warn(`Sync queue full - discarded oldest item: ${discarded.key}`); } // Persist trimmed queue await AsyncStorage.setItem('syncQueue', JSON.stringify(this.syncQueue)); } } async processQueue() { const failedItems = []; for (const item of this.syncQueue) { try { await this.syncToServer(item.key, item.data); } catch (err) { console.error('Sync failed:', err); failedItems.push(item); } } this.syncQueue = failedItems; if (failedItems.length === 0) { await AsyncStorage.removeItem('syncQueue'); } else { await AsyncStorage.setItem('syncQueue', JSON.stringify(failedItems)); } } }
Conflict Resolution
function resolveConflict(local, server) { // Last-write-wins if (local.updatedAt > server.updatedAt) return local; return server; // Or merge changes // return { ...server, ...local }; }
UI Indicators
function OfflineIndicator() { const [isOnline, setIsOnline] = useState(true); useEffect(() => { return NetInfo.addEventListener(state => { setIsOnline(state.isConnected); }); }, []); if (isOnline) return null; return ( <View style={styles.banner}> <Text>You're offline. Changes will sync when connected.</Text> </View> ); }
Best Practices
- Cache frequently accessed data locally
- Queue actions for later sync
- Show clear offline indicators
- Handle sync conflicts gracefully
- Compress stored data
- Test offline scenarios thoroughly
Native Implementations
See references/native-implementations.md for:
- iOS Core Data with sync manager
- Android Room database with WorkManager sync
Avoid
- Assuming connectivity
- Losing data on sync failures
- Unbounded queue growth
- Syncing sensitive data insecurely