Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 232 additions & 0 deletions docs/concepts/caching.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
---
title: "Caching & Offline Support"
description: "Learn how Stac intelligently caches screens for offline access, faster loading, and smarter network usage"
---

Stac includes a powerful caching system that stores screens locally, enabling offline access, instant loading, and more efficient network usage. The caching layer works automatically with the `Stac` widget and can be customized for different use cases.

## How Caching Works

When you use the `Stac` widget to fetch screens from Stac Cloud:

1. **First Load**: The screen is fetched from the network and stored in local cache
2. **Subsequent Loads**: Based on your cache strategy, Stac returns cached data and/or fetches fresh data
3. **Version Tracking**: Each cached screen stores its version number, enabling smart updates
4. **Background Updates**: Fresh data can be fetched in the background without blocking the UI

## Cache Strategies

Stac provides five caching strategies to fit different use cases:

### 1. Optimistic (Default)

Returns cached data immediately while fetching updates in the background. Best for fast perceived performance.

```dart
Stac(
routeName: '/home',
cacheConfig: StacCacheConfig(
strategy: StacCacheStrategy.optimistic,
),
)
```

**Behavior:**
- ✅ Returns cached data instantly (even if expired)
- ✅ Fetches fresh data in background
- ✅ Updates cache for next load
- ⚡ Fastest perceived loading

**Best for:** UI layouts, content screens, any screen where instant loading matters more than showing the absolute latest data.

### 2. Cache First

Uses cached data if available and valid, only fetches from network when cache is invalid or missing.

```dart
Stac(
routeName: '/home',
cacheConfig: StacCacheConfig(
strategy: StacCacheStrategy.cacheFirst,
maxAge: Duration(hours: 24),
),
)
```

**Behavior:**
- ✅ Uses cache if valid (not expired)
- ✅ Fetches from network only when cache is invalid
- ✅ Optionally refreshes in background
- 📱 Great for offline-first apps

**Best for:** Offline-first apps, content that doesn't change frequently, reducing network usage.

### 3. Network First

Always tries the network first, falls back to cache if network fails.

```dart
Stac(
routeName: '/home',
cacheConfig: StacCacheConfig(
strategy: StacCacheStrategy.networkFirst,
),
)
```

**Behavior:**
- ✅ Always fetches fresh data first
- ✅ Falls back to cache on network error
- ✅ Ensures latest content when online
- 🌐 Requires network for best experience

**Best for:** Real-time data, frequently changing content, screens where freshness is critical.

### 4. Cache Only

Only uses cached data, never makes network requests. Throws an error if no cache exists.

```dart
Stac(
routeName: '/home',
cacheConfig: StacCacheConfig(
strategy: StacCacheStrategy.cacheOnly,
),
)
```

**Behavior:**
- ✅ Never makes network requests
- ✅ Instant loading from cache
- ❌ Fails if no cached data exists
- 📴 Perfect for offline mode

**Best for:** Offline-only mode, airplane mode, when you've pre-cached screens.

### 5. Network Only

Always fetches from network, never uses or updates cache.

```dart
Stac(
routeName: '/home',
cacheConfig: StacCacheConfig(
strategy: StacCacheStrategy.networkOnly,
),
)
```

**Behavior:**
- ✅ Always fresh data
- ❌ Fails without network
- ❌ No offline support
- 🔒 Good for sensitive data

**Best for:** Sensitive data that shouldn't be cached, real-time dashboards, authentication screens.

## Cache Configuration

### StacCacheConfig Properties

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `strategy` | `StacCacheStrategy` | `optimistic` | The caching strategy to use |
| `maxAge` | `Duration?` | `null` | Maximum age before cache is considered expired. `null` means no time-based expiration |
| `refreshInBackground` | `bool` | `true` | Whether to fetch fresh data in background when cache is valid |
| `staleWhileRevalidate` | `bool` | `false` | Whether to use expired cache while fetching fresh data |

### Setting Cache Duration

Control how long cached data remains valid:

```dart
// Cache valid for 1 hour
Stac(
routeName: '/home',
cacheConfig: StacCacheConfig(
strategy: StacCacheStrategy.cacheFirst,
maxAge: Duration(hours: 1),
),
)

// Cache valid for 7 days
Stac(
routeName: '/settings',
cacheConfig: StacCacheConfig(
strategy: StacCacheStrategy.cacheFirst,
maxAge: Duration(days: 7),
),
)

// Cache never expires (only version-based updates)
Stac(
routeName: '/static-page',
cacheConfig: StacCacheConfig(
strategy: StacCacheStrategy.cacheFirst,
maxAge: null, // No time-based expiration
),
)
```

### Background Refresh

Keep cache fresh without blocking the UI:

```dart
Stac(
routeName: '/home',
cacheConfig: StacCacheConfig(
strategy: StacCacheStrategy.cacheFirst,
maxAge: Duration(hours: 1),
refreshInBackground: true, // Fetch updates silently
),
)
```

When `refreshInBackground` is `true`:
- Valid cache is returned immediately
- Fresh data is fetched in background
- Cache is updated for next load
- User sees instant loading with eventual consistency

### Stale While Revalidate

Show expired cache while fetching fresh data:

```dart
Stac(
routeName: '/home',
cacheConfig: StacCacheConfig(
strategy: StacCacheStrategy.cacheFirst,
maxAge: Duration(hours: 1),
staleWhileRevalidate: true, // Use expired cache while fetching
),
)
```

This is useful when:
- You prefer showing something over a loading spinner
- Content staleness is acceptable for a brief period
- Network is slow or unreliable

## Strategy Comparison

| Strategy | Initial Load | Subsequent Load | Offline Support | Best For |
|----------|--------------|-----------------|-----------------|----------|
| `optimistic` | Network → Cache | Cache (bg update) | ✅ Yes | Fast UX |
| `cacheFirst` | Cache or Network | Cache | ✅ Yes | Offline apps |
| `networkFirst` | Network | Network (cache fallback) | ⚠️ Fallback only | Fresh data |
| `cacheOnly` | Cache only | Cache only | ✅ Yes | Offline mode |
| `networkOnly` | Network only | Network only | ❌ No | Sensitive data |

## Version-Based Updates

Stac tracks version numbers for each cached screen. When you deploy updates to Stac Cloud:

1. The server assigns a new version number
2. Background fetches detect the new version
3. Cache is updated with new content
4. Next load shows updated screen

This ensures users get updates without manual intervention while maintaining fast loading times.

1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"icon": "book",
"pages": [
"concepts/rendering_stac_widgets",
"concepts/caching",
"concepts/custom_widgets",
"concepts/custom_actions"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import FlutterMacOS
import Foundation

import path_provider_foundation
import shared_preferences_foundation
import sqflite_darwin

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
}
67 changes: 64 additions & 3 deletions examples/counter_example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,11 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
freezed:
dependency: "direct dev"
description:
Expand Down Expand Up @@ -627,6 +632,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.28.0"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "07d552dbe8e71ed720e5205e760438ff4ecfb76ec3b32ea664350e2ca4b0c43b"
url: "https://pub.dev"
source: hosted
version: "2.4.16"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
url: "https://pub.dev"
source: hosted
version: "2.5.6"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shelf:
dependency: transitive
description:
Expand Down Expand Up @@ -726,7 +787,7 @@ packages:
path: "../../packages/stac"
relative: true
source: path
version: "1.1.0"
version: "1.1.2"
stac_core:
dependency: "direct overridden"
description:
Expand Down Expand Up @@ -925,5 +986,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.29.0"
dart: ">=3.9.0 <4.0.0"
flutter: ">=3.35.0"
7 changes: 7 additions & 0 deletions examples/movie_app/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,33 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS

DEPENDENCIES:
- Flutter (from `Flutter`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)

EXTERNAL SOURCES:
Flutter:
:path: Flutter
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"

SPEC CHECKSUMS:
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0

PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import FlutterMacOS
import Foundation

import path_provider_foundation
import shared_preferences_foundation
import sqflite_darwin

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
}
Loading