mobile-offline-support

Implement offline-first mobile apps with local storage, sync strategies, and conflict resolution. Covers AsyncStorage, Realm, SQLite, and background sync patterns.

16 stars

Best use case

mobile-offline-support is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Implement offline-first mobile apps with local storage, sync strategies, and conflict resolution. Covers AsyncStorage, Realm, SQLite, and background sync patterns.

Teams using mobile-offline-support should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/mobile-offline-support/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/development/mobile-offline-support/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/mobile-offline-support/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How mobile-offline-support Compares

Feature / Agentmobile-offline-supportStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Implement offline-first mobile apps with local storage, sync strategies, and conflict resolution. Covers AsyncStorage, Realm, SQLite, and background sync patterns.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Mobile Offline Support

## Overview

Design offline-first mobile applications that provide seamless user experience regardless of connectivity.

## When to Use

- Building apps that work without internet connection
- Implementing seamless sync when connectivity returns
- Handling data conflicts between device and server
- Reducing server load with intelligent caching
- Improving app responsiveness with local storage

## Instructions

### 1. **React Native Offline Storage**

```javascript
import AsyncStorage from '@react-native-async-storage/async-storage';
import NetInfo from '@react-native-community/netinfo';

class StorageManager {
  static async saveItems(items) {
    try {
      await AsyncStorage.setItem(
        'items_cache',
        JSON.stringify({ data: items, timestamp: Date.now() })
      );
    } catch (error) {
      console.error('Failed to save items:', error);
    }
  }

  static async getItems() {
    try {
      const data = await AsyncStorage.getItem('items_cache');
      return data ? JSON.parse(data) : null;
    } catch (error) {
      console.error('Failed to retrieve items:', error);
      return null;
    }
  }

  static async queueAction(action) {
    try {
      const queue = await AsyncStorage.getItem('action_queue');
      const actions = queue ? JSON.parse(queue) : [];
      actions.push({ ...action, id: Date.now(), attempts: 0 });
      await AsyncStorage.setItem('action_queue', JSON.stringify(actions));
    } catch (error) {
      console.error('Failed to queue action:', error);
    }
  }

  static async getActionQueue() {
    try {
      const queue = await AsyncStorage.getItem('action_queue');
      return queue ? JSON.parse(queue) : [];
    } catch (error) {
      return [];
    }
  }

  static async removeFromQueue(actionId) {
    try {
      const queue = await AsyncStorage.getItem('action_queue');
      const actions = queue ? JSON.parse(queue) : [];
      const filtered = actions.filter(a => a.id !== actionId);
      await AsyncStorage.setItem('action_queue', JSON.stringify(filtered));
    } catch (error) {
      console.error('Failed to remove from queue:', error);
    }
  }
}

class OfflineAPIService {
  async fetchItems() {
    const isOnline = await this.checkConnectivity();

    if (isOnline) {
      try {
        const response = await fetch('https://api.example.com/items');
        const items = await response.json();
        await StorageManager.saveItems(items);
        return items;
      } catch (error) {
        const cached = await StorageManager.getItems();
        return cached?.data || [];
      }
    } else {
      const cached = await StorageManager.getItems();
      return cached?.data || [];
    }
  }

  async createItem(item) {
    const isOnline = await this.checkConnectivity();

    if (isOnline) {
      try {
        const response = await fetch('https://api.example.com/items', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(item)
        });
        const created = await response.json();
        return { success: true, data: created };
      } catch (error) {
        await StorageManager.queueAction({
          type: 'CREATE_ITEM',
          payload: item
        });
        return { success: false, queued: true };
      }
    } else {
      await StorageManager.queueAction({
        type: 'CREATE_ITEM',
        payload: item
      });
      return { success: false, queued: true };
    }
  }

  async syncQueue() {
    const queue = await StorageManager.getActionQueue();

    for (const action of queue) {
      try {
        await this.executeAction(action);
        await StorageManager.removeFromQueue(action.id);
      } catch (error) {
        action.attempts = (action.attempts || 0) + 1;
        if (action.attempts > 3) {
          await StorageManager.removeFromQueue(action.id);
        }
      }
    }
  }

  private async executeAction(action) {
    switch (action.type) {
      case 'CREATE_ITEM':
        return fetch('https://api.example.com/items', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(action.payload)
        });
      default:
        return Promise.reject(new Error('Unknown action type'));
    }
  }

  async checkConnectivity() {
    const state = await NetInfo.fetch();
    return state.isConnected ?? false;
  }
}

export function OfflineListScreen() {
  const [items, setItems] = useState([]);
  const [isOnline, setIsOnline] = useState(true);
  const [syncing, setSyncing] = useState(false);
  const apiService = new OfflineAPIService();

  useFocusEffect(
    useCallback(() => {
      loadItems();
      const unsubscribe = NetInfo.addEventListener(state => {
        setIsOnline(state.isConnected ?? false);
        if (state.isConnected) {
          syncQueue();
        }
      });

      return unsubscribe;
    }, [])
  );

  const loadItems = async () => {
    const items = await apiService.fetchItems();
    setItems(items);
  };

  const syncQueue = async () => {
    setSyncing(true);
    await apiService.syncQueue();
    await loadItems();
    setSyncing(false);
  };

  return (
    <View style={styles.container}>
      {!isOnline && <Text style={styles.offline}>Offline Mode</Text>}
      {syncing && <ActivityIndicator size="large" />}
      <FlatList
        data={items}
        renderItem={({ item }) => <ItemCard item={item} />}
        keyExtractor={item => item.id}
      />
    </View>
  );
}
```

### 2. **iOS Core Data Implementation**

```swift
import CoreData

class PersistenceController {
  static let shared = PersistenceController()

  let container: NSPersistentContainer

  init(inMemory: Bool = false) {
    container = NSPersistentContainer(name: "MyApp")

    if inMemory {
      container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
    }

    container.loadPersistentStores { _, error in
      if let error = error as NSError? {
        print("Core Data load error: \(error)")
      }
    }

    container.viewContext.automaticallyMergesChangesFromParent = true
  }

  func save(_ context: NSManagedObjectContext = PersistenceController.shared.container.viewContext) {
    if context.hasChanges {
      do {
        try context.save()
      } catch {
        print("Save error: \(error)")
      }
    }
  }
}

// Core Data Models
@NSManaged class ItemEntity: NSManagedObject {
  @NSManaged var id: String
  @NSManaged var title: String
  @NSManaged var description: String?
  @NSManaged var isSynced: Bool
}

@NSManaged class ActionQueueEntity: NSManagedObject {
  @NSManaged var id: UUID
  @NSManaged var type: String
  @NSManaged var payload: Data?
  @NSManaged var createdAt: Date
}

class OfflineSyncManager: NSObject, ObservableObject {
  @Published var isOnline = true
  @Published var isSyncing = false

  private let networkMonitor = NWPathMonitor()
  private let persistenceController = PersistenceController.shared

  override init() {
    super.init()
    setupNetworkMonitoring()
  }

  private func setupNetworkMonitoring() {
    networkMonitor.pathUpdateHandler = { [weak self] path in
      DispatchQueue.main.async {
        self?.isOnline = path.status == .satisfied
        if path.status == .satisfied {
          self?.syncWithServer()
        }
      }
    }

    let queue = DispatchQueue(label: "NetworkMonitor")
    networkMonitor.start(queue: queue)
  }

  func saveItem(_ item: Item) {
    let context = persistenceController.container.viewContext
    let entity = ItemEntity(context: context)
    entity.id = item.id
    entity.title = item.title
    entity.isSynced = false

    persistenceController.save(context)

    if isOnline {
      syncItem(item)
    }
  }

  func syncWithServer() {
    isSyncing = true
    let context = persistenceController.container.viewContext
    let request: NSFetchRequest<ActionQueueEntity> = ActionQueueEntity.fetchRequest()

    do {
      let pendingActions = try context.fetch(request)
      for action in pendingActions {
        context.delete(action)
      }
      persistenceController.save(context)
    } catch {
      print("Sync error: \(error)")
    }

    isSyncing = false
  }
}
```

### 3. **Android Room Database**

```kotlin
@Entity(tableName = "items")
data class ItemEntity(
  @PrimaryKey val id: String,
  val title: String,
  val description: String?,
  val isSynced: Boolean = false
)

@Entity(tableName = "action_queue")
data class ActionQueueEntity(
  @PrimaryKey val id: Long = System.currentTimeMillis(),
  val type: String,
  val payload: String,
  val createdAt: Long = System.currentTimeMillis()
)

@Dao
interface ItemDao {
  @Insert(onConflict = OnConflictStrategy.REPLACE)
  suspend fun insertItem(item: ItemEntity)

  @Query("SELECT * FROM items")
  fun getAllItems(): Flow<List<ItemEntity>>

  @Update
  suspend fun updateItem(item: ItemEntity)
}

@Dao
interface ActionQueueDao {
  @Insert
  suspend fun insertAction(action: ActionQueueEntity)

  @Query("SELECT * FROM action_queue ORDER BY createdAt ASC")
  suspend fun getAllActions(): List<ActionQueueEntity>

  @Delete
  suspend fun deleteAction(action: ActionQueueEntity)
}

@Database(entities = [ItemEntity::class, ActionQueueEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
  abstract fun itemDao(): ItemDao
  abstract fun actionQueueDao(): ActionQueueDao
}

@HiltViewModel
class OfflineItemsViewModel @Inject constructor(
  private val itemDao: ItemDao,
  private val actionQueueDao: ActionQueueDao,
  private val connectivityManager: ConnectivityManager
) : ViewModel() {
  private val _items = MutableStateFlow<List<Item>>(emptyList())
  val items: StateFlow<List<Item>> = _items.asStateFlow()

  init {
    viewModelScope.launch {
      itemDao.getAllItems().collect { entities ->
        _items.value = entities.map { it.toItem() }
      }
    }
    observeNetworkConnectivity()
  }

  fun saveItem(item: Item) {
    viewModelScope.launch {
      val entity = item.toEntity()
      itemDao.insertItem(entity)

      if (isNetworkAvailable()) {
        syncItem(item)
      } else {
        actionQueueDao.insertAction(
          ActionQueueEntity(
            type = "CREATE_ITEM",
            payload = Json.encodeToString(item)
          )
        )
      }
    }
  }

  private fun observeNetworkConnectivity() {
    val networkRequest = NetworkRequest.Builder()
      .addCapability(NET_CAPABILITY_INTERNET)
      .build()

    connectivityManager.registerNetworkCallback(
      networkRequest,
      object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
          viewModelScope.launch { syncQueue() }
        }
      }
    )
  }

  private suspend fun syncQueue() {
    val queue = actionQueueDao.getAllActions()
    for (action in queue) {
      try {
        actionQueueDao.deleteAction(action)
      } catch (e: Exception) {
        println("Sync error: ${e.message}")
      }
    }
  }

  private fun isNetworkAvailable(): Boolean {
    val activeNetwork = connectivityManager.activeNetwork ?: return false
    val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
    return capabilities.hasCapability(NET_CAPABILITY_INTERNET)
  }
}
```

## Best Practices

### ✅ DO
- Implement robust local storage
- Use automatic sync when online
- Provide visual feedback for offline status
- Queue actions for later sync
- Handle conflicts gracefully
- Cache frequently accessed data
- Implement proper error recovery
- Test offline scenarios thoroughly
- Use compression for large data
- Monitor storage usage

### ❌ DON'T
- Assume constant connectivity
- Sync large files frequently
- Ignore storage limitations
- Force unnecessary syncing
- Lose data on offline mode
- Store sensitive data unencrypted
- Accumulate infinite queue items
- Ignore sync failures silently
- Sync in tight loops
- Deploy without offline testing

Related Skills

mobile_react_native

16
from diegosouzapw/awesome-omni-skill

React Native best practices, hooks, navigation ve performance optimization.

mobile-ui-development-rule

16
from diegosouzapw/awesome-omni-skill

General rules pertaining to Mobile UI development. Covers UI/UX best practices, state management, and navigation patterns.

mobile-security-expert

16
from diegosouzapw/awesome-omni-skill

移动安全漏洞挖掘知识库,基于HackerOne公开报告提供Android和iOS应用的漏洞挖掘手法、技术细节和代码模式分析;用于安全研究人员和漏洞挖掘者学习参考、代码审计和漏洞检测指导。

mobile-security-coder

16
from diegosouzapw/awesome-omni-skill

Expert in secure mobile coding practices specializing in input validation, WebView security, and mobile-specific security patterns.

mobile-guide

16
from diegosouzapw/awesome-omni-skill

Comprehensive mobile development guide for iOS, Android, React Native, and Flutter. Includes Swift, Kotlin, and cross-platform frameworks. Use when building mobile applications, iOS, Android, or cross-platform apps.

mobile-games

16
from diegosouzapw/awesome-omni-skill

Mobile game development principles. Touch input, battery, performance, app stores.

mobile-frontend

16
from diegosouzapw/awesome-omni-skill

React Native patterns, NativeWind styling, React Native Reusables components, mobile-specific patterns

mobile-first-design-rules

16
from diegosouzapw/awesome-omni-skill

Focuses on rules and best practices for mobile-first design and responsive typography using tailwind.

mobile-development

16
from diegosouzapw/awesome-omni-skill

Cross-platform and native mobile development. Frameworks: React Native, Flutter, Swift/SwiftUI, Kotlin/Jetpack Compose. Capabilities: mobile UI, offline-first architecture, push notifications, deep linking, biometrics, app store deployment. Actions: build, create, implement, optimize, test, deploy mobile apps. Keywords: iOS, Android, React Native, Flutter, Swift, Kotlin, mobile app, offline sync, push notification, deep link, biometric auth, App Store, Play Store, iOS HIG, Material Design, battery optimization, memory management, mobile performance. Use when: building mobile apps, implementing mobile-first UX, choosing native vs cross-platform, optimizing battery/memory/network, deploying to app stores, handling mobile-specific features.

mobile-developer

16
from diegosouzapw/awesome-omni-skill

Develop React Native, Flutter, or native mobile apps with modern architecture patterns. Masters cross-platform development, native integrations, offline sync, and app store optimization. Use PROACTIVELY for mobile features, cross-platform code, or app optimization.

mobile-design

16
from diegosouzapw/awesome-omni-skill

Mobile-first design and engineering doctrine for iOS and Android apps. Covers touch interaction, performance, platform conventions, offline behavior, and mobile-specific decision-making. Teaches pr...

mobile-app-testing

16
from diegosouzapw/awesome-omni-skill

Comprehensive mobile app testing strategies for iOS and Android. Covers unit tests, UI tests, integration tests, performance testing, and test automation with Detox, Appium, and XCTest.