agentsclimarketplace

Offline first strategy

Skill almasumdev/awesome-mobile-agent-skills/.github/skills/offline/offline-first-strategy

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.From its SKILL.md

Install
npx -y skills add almasumdev/awesome-mobile-agent-skills --skill offline-first-strategy

Assembled 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.

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

TierWhereLifespanExample
L0: in-memoryapp processforeground sessioncurrent screen view model
L1: on-disk structuredSQLite, Realm, Core Data, Room, Drift, SQLDelightpersistentlists, entities, search indices
L2: on-disk blobfile cache directorypurgeableimages, videos, exports
L3: secureKeychain / Keystorepersistent, encryptedtokens, keys
Remoteserverauthoritative for shared datatransactional 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.

  1. Write to the local store immediately with an isPending flag.
  2. Render the UI from the local store; the change appears instantly.
  3. Enqueue the write to an outbox.
  4. A sync worker drains the outbox with retry/backoff.
  5. 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 adb or xcrun 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.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.