<< All versions
Skill v1.0.1
currentAutomated scan100/100majiayu000/claude-skill-registry-data/mobile-offline-support-iskenkenya-1panel-client-4
3 files
──Details
PublishedJune 4, 2026 at 01:42 AM
Content Hashsha256:9a5703531fc92429...
Git SHAe5cbd1ef8ccd
Bump Typepatch
──Files
Files (1 file, 11.9 KB)
SKILL.md11.9 KBactive
SKILL.md · 468 lines · 11.9 KB
version: "1.0.1" name: mobile-offline-support description: Implement offline-first mobile apps with local storage, sync strategies, and conflict resolution. Covers AsyncStorage, Realm, SQLite, and background sync patterns.
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" />}<FlatListdata={items}renderItem={({ item }) => <ItemCard item={item} />}keyExtractor={item => item.id}/></View>);}
2. iOS Core Data Implementation
swift
import CoreDataclass PersistenceController {static let shared = PersistenceController()let container: NSPersistentContainerinit(inMemory: Bool = false) {container = NSPersistentContainer(name: "MyApp")if inMemory {container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")}container.loadPersistentStores { _, error inif 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 = falseprivate let networkMonitor = NWPathMonitor()private let persistenceController = PersistenceController.sharedoverride init() {super.init()setupNetworkMonitoring()}private func setupNetworkMonitoring() {networkMonitor.pathUpdateHandler = { [weak self] path inDispatchQueue.main.async {self?.isOnline = path.status == .satisfiedif path.status == .satisfied {self?.syncWithServer()}}}let queue = DispatchQueue(label: "NetworkMonitor")networkMonitor.start(queue: queue)}func saveItem(_ item: Item) {let context = persistenceController.container.viewContextlet entity = ItemEntity(context: context)entity.id = item.identity.title = item.titleentity.isSynced = falsepersistenceController.save(context)if isOnline {syncItem(item)}}func syncWithServer() {isSyncing = truelet context = persistenceController.container.viewContextlet 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())@Daointerface ItemDao {@Insert(onConflict = OnConflictStrategy.REPLACE)suspend fun insertItem(item: ItemEntity)@Query("SELECT * FROM items")fun getAllItems(): Flow<List<ItemEntity>>@Updatesuspend fun updateItem(item: ItemEntity)}@Daointerface ActionQueueDao {@Insertsuspend fun insertAction(action: ActionQueueEntity)@Query("SELECT * FROM action_queue ORDER BY createdAt ASC")suspend fun getAllActions(): List<ActionQueueEntity>@Deletesuspend fun deleteAction(action: ActionQueueEntity)}@Database(entities = [ItemEntity::class, ActionQueueEntity::class], version = 1)abstract class AppDatabase : RoomDatabase() {abstract fun itemDao(): ItemDaoabstract fun actionQueueDao(): ActionQueueDao}@HiltViewModelclass 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 falseval capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return falsereturn 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