
Mobile Offline Support
- 482 installs
- 305 repo stars
- Updated March 4, 2026
- aj-geddes/useful-ai-prompts
mobile-offline-support is an agent skill that guides developers through offline-first mobile architecture with local storage, sync strategies, and conflict resolution for React Native and similar stacks.
About
mobile-offline-support is a Claude Code skill from aj-geddes/useful-ai-prompts for building offline-first mobile applications that stay usable without connectivity and reconcile data when the network returns. The skill walks through local persistence options—AsyncStorage, Realm, and SQLite—and patterns for background sync, queueing writes, and conflict resolution so React Native apps degrade gracefully offline. Developers reach for mobile-offline-support when adding reliable caching, sync-on-reconnect, or merge logic to field apps, travel tools, or any mobile product where dropped connections are common.
- Covers AsyncStorage, Realm, and SQLite for local persistence
- Implements intelligent background sync and conflict resolution patterns
- Reduces server load through smart caching strategies
- Provides seamless user experience when connectivity is lost
- Includes NetInfo integration for real-time connection awareness
Mobile Offline Support by the numbers
- 482 all-time installs (skills.sh)
- Ranked #306 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aj-geddes/useful-ai-prompts --skill mobile-offline-supportAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 482 |
|---|---|
| repo stars | ★ 305 |
| Last updated | March 4, 2026 |
| Repository | aj-geddes/useful-ai-prompts ↗ |
How do you build offline-first React Native apps?
Add reliable offline-first capabilities to React Native and similar mobile apps.
Who is it for?
Mobile developers shipping React Native apps that must read and write data reliably without constant network access.
Skip if: Teams building server-only APIs or web SPAs with no local persistence requirements should skip mobile-offline-support.
When should I use this skill?
A developer asks to add offline mode, local caching, background sync, or conflict handling to a React Native or similar mobile app.
What you get
Offline storage layer, sync strategy, conflict-resolution rules, and background sync implementation patterns
- Offline storage implementation
- Sync strategy
- Conflict resolution rules
By the numbers
- Covers 3 local storage backends: AsyncStorage, Realm, and SQLite
Files
Mobile Offline Support
Table of Contents
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
Quick Start
Minimal working example:
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;
}
}
// ... (see reference guides for full implementation)Reference Guides
Detailed implementations in the references/ directory:
| Guide | Contents |
|---|---|
| React Native Offline Storage | React Native Offline Storage |
| iOS Core Data Implementation | iOS Core Data Implementation |
| Android Room Database | Android Room Database |
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
Android Room Database
Android Room Database
@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)
}
}iOS Core Data Implementation
iOS Core Data Implementation
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
}
}React Native Offline Storage
React Native Offline Storage
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>
);
}// Component: [Name]
// TODO: Customize for your framework (React, Vue, Svelte, etc.)
import React from 'react';
interface Props {
// TODO: Define props
}
export function ComponentName({ }: Props) {
// TODO: Add state and effects
return (
<div>
{/* TODO: Add component markup */}
</div>
);
}
Related skills
How it compares
Pick mobile-offline-support over generic mobile UI skills when the task is persistence, sync queues, and merge logic—not layout or navigation.
FAQ
What storage backends does mobile-offline-support cover?
mobile-offline-support covers AsyncStorage, Realm, and SQLite for React Native and similar mobile stacks. The skill compares when to use each store and how to pair them with background sync and conflict resolution patterns.
When should developers use mobile-offline-support?
mobile-offline-support fits apps that must work without internet, queue writes offline, and merge changes on reconnect. Use it when building field, travel, or productivity mobile apps where connectivity is intermittent.