Offline first strategy
Skill almasumdev/awesome-mobile-agent-skills/.github/skills/offline/offline-first-strategy
Stack-agnostic agent skills and workflows shared across iOS, Android, Flutter, React Native, and KMP.
npx -y skills add almasumdev/awesome-mobile-agent-skills --skill offline-first-strategyAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Designing a mobile app as offline-first - caching tiers, authoritative sources, read and write paths, and the boundaries between device and server. Use when a feature needs to work without connectivity or when diagnosing stale/inconsistent data.
SKILL.md
6.0 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it
Offline-First Strategy
Instructions
Mobile devices are flaky by nature. Treat the network as optional. Offline-first apps feel fast, survive tunnels and airplane mode, and reduce server load. The trick is picking the authoritative source per data kind and building a predictable read/write path around it.
1. Tiers of Cache
| Tier | Where | Lifespan | Example |
|---|---|---|---|
| L0: in-memory | app process | foreground session | current screen view model |
| L1: on-disk structured | SQLite, Realm, Core Data, Room, Drift, SQLDelight | persistent | lists, entities, search indices |
| L2: on-disk blob | file cache directory | purgeable | images, videos, exports |
| L3: secure | Keychain / Keystore | persistent, encrypted | tokens, keys |
| Remote | server | authoritative for shared data | transactional writes |
Pick tiers explicitly per data type; do not let a JSON in SharedPreferences grow into a database.
2. Authoritative Source Rule
For every data kind, declare one authoritative source:
- User-owned local data (notes draft, reading position, settings): device is authoritative, server is backup.
- Shared collaborative data (chat messages, shared docs): server is authoritative, device is cache.
- Reference data (catalog, pricing): server authoritative, device caches with TTL.
- Secrets (tokens): keychain authoritative, memory is ephemeral.
Writing this down prevents "whose value wins" debates during conflict resolution.
3. Read Path
UI -> Repository.read(id) -> emits Loading -> emits Cached (fast) -> refresh if stale -> emits Fresh
- Return cached data immediately on subscribe.
- Fire a revalidation in parallel if the cache is stale.
- Emit new state when fresh data arrives. Never block the UI on the network when a cache exists.
// Kotlin (Flow)
fun observeArticle(id: String): Flow<Resource<Article>> = flow {
val cached = dao.get(id)
if (cached != null) emit(Resource.Success(cached))
runCatching { api.article(id) }
.onSuccess { fresh -> dao.upsert(fresh); emit(Resource.Success(fresh)) }
.onFailure { if (cached == null) emit(Resource.Error(it)) }
}
// Swift
func observeArticle(id: String) -> AsyncStream<Resource<Article>> {
AsyncStream { cont in
Task {
if let cached = try? await store.get(id) { cont.yield(.success(cached)) }
do {
let fresh = try await api.article(id)
try await store.upsert(fresh)
cont.yield(.success(fresh))
} catch {
// keep cached view; surface error only if nothing is cached
}
cont.finish()
}
}
}
// Dart
Stream<Resource<Article>> observeArticle(String id) async* {
final cached = await dao.get(id);
if (cached != null) yield Resource.success(cached);
try {
final fresh = await api.article(id);
await dao.upsert(fresh);
yield Resource.success(fresh);
} catch (e) {
if (cached == null) yield Resource.error(e);
}
}
4. Write Path
Writes should never block the UI on the network.
- Write to the local store immediately with an
isPendingflag. - Render the UI from the local store; the change appears instantly.
- Enqueue the write to an outbox.
- A sync worker drains the outbox with retry/backoff.
- On ack, clear the pending flag; on permanent failure, mark conflict and surface it.
This is the outbox pattern and it is the single biggest win for perceived performance.
5. Cache Invalidation
Choose an invalidation strategy per data kind:
- TTL: staleAfter + hardExpireAfter. Simplest.
- ETag / Last-Modified: server-led freshness; supports 304 responses.
- Subscription / push: server tells you when to invalidate (websocket, SSE, silent push).
- Versioned bundles: for reference data, bump a version key and purge.
Document the policy per entity.
6. Network Awareness
- Detect connectivity changes but do not use them as the only signal. A reachable API may still fail.
- On regaining connectivity, drain the outbox and revalidate visible screens only (not the whole cache).
- Surface a subtle offline indicator; avoid full-screen blockers unless the user explicitly chose an online-only feature.
7. Storage Budgets
- Set a cap (for example 200 MB) and evict LRU when exceeded.
- Keep sensitive data out of L2 blob caches.
- Never store unbounded history on-device.
- Provide a "clear cache" action somewhere in settings for support.
8. Testing Offline
- Unit test the repository with a fake remote that can be in three states: ok, slow, failing.
- UI tests toggle airplane mode via
adborxcrun simctl status_bar override. - Seed a realistic cache on device before running offline flows.
- Track a CI job that runs the critical-path suite with network disabled.
9. Anti-Patterns
- Writing to the network first and then to the cache. The UI is sluggish and inconsistent.
- Storing derived views on disk (search results, sorted lists). Store facts; derive on read.
- Global caches with no eviction policy.
- Silent failures when offline. Surface the pending state in the UI.
- Shipping features that only work online without saying so.
Checklist
- Each data kind has a named authoritative source.
- Reads return cache first, then revalidate.
- Writes use an outbox with retry/backoff and a pending UI state.
- Cache invalidation policy is documented per entity.
- Secrets are in keychain/keystore, never in the main database.
- Storage has a cap and an eviction policy.
- Connectivity changes drive revalidation, not feature gating.
- Critical paths have offline integration tests.
- Offline indicator is present and non-blocking.
Gives 0 of the 12 instructions most roadmap strategy skills give in ~1.3k tokens
Counted across 591 of the 672 authors here whose files we hold, read 2026-08-06
- read product marketing context before asking questionsin 21 of 591, across 10 files
- base price on perceived value, not costin 15 of 591, across 4 files
- compact after finalizing a planin 14 of 591, across 9 files
- differentiate tiers using features, limits, or supportin 14 of 591, across 3 files
- use Van Westendorp to find acceptable price rangein 13 of 591, across 2 files
- use MaxDiff to identify highly valued featuresin 13 of 591, across 2 files
- map topics to buyer journey stagesin 12 of 591, across 6 files
- Extract domain capabilities and classify subdomainsin 11 of 591, across 1 file
- Define bounded contexts around consistency and ownershipin 11 of 591, across 1 file
- Establish a ubiquitous language glossary and anti-termsin 11 of 591, across 1 file
- Capture context boundaries in ADRs before implementationin 11 of 591, across 1 file
- Open the strategic design template if neededin 11 of 591, across 1 file
Said here and by no other author read
- treat the network as optional
- pick cache tiers explicitly per data type
- declare one authoritative source per data kind
- return cached data immediately on read
- never block the UI on the network
- write to the local store first
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.