mcpField
M
mobile-offline-support

mobile-offline-support

github.com/secondsky开发242406 次安装5 周前更新
代码仓库

关于 mobile-offline-support

面向离线优先移动应用开发者的技能,提供 React Native 示例及 iOS、Android 原生实现参考,涵盖本地数据、操作排队、同步和冲突处理。

主要功能

  1. 01本地数据缓存
  2. 02操作排队以便稍后同步
  3. 03连接状态指示
  4. 04同步冲突处理
  5. 05iOS 和 Android 原生实现参考

适用场景

  1. 01支持无网络时使用应用
  2. 02重新联网后同步排队变更
  3. 03处理本地与服务器更新冲突
  4. 04向用户显示当前连接状态

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/secondsky/claude-skills --skill mobile-offline-support

安装后在 Claude Code 中输入/mobile-offline-support确认已加载。

附带文件references/native-implementations.md

详细介绍

移动端离线支持

通过本地存储和同步构建离线优先的移动应用。

React Native 实现

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));
    }
  }
}

冲突解决

function resolveConflict(local, server) {
  // Last-write-wins
  if (local.updatedAt > server.updatedAt) return local;
  return server;

  // Or merge changes
  // return { ...server, ...local };
}

UI 指示器

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>
  );
}

最佳实践

  • 将频繁访问的数据缓存在本地
  • 将操作加入队列,以便稍后同步
  • 清晰显示离线状态
  • 妥善处理同步冲突
  • 压缩存储的数据
  • 充分测试离线场景

原生实现

参见 references/native-implementations.md,了解:

  • 使用同步管理器的 iOS Core Data
  • 使用 WorkManager 同步的 Android Room 数据库

避免

  • 假定网络始终连接
  • 同步失败时丢失数据
  • 队列无限增长
  • 以不安全的方式同步敏感数据

评论

支持 Markdown