Skip to content

Conversation

@divyanshub024
Copy link
Member

@divyanshub024 divyanshub024 commented Nov 25, 2025

Description

  • Implemented StacCacheConfig and StacCacheStrategy to control how screens are cached and fetched.
  • Added five cache strategies: optimistic (default), cacheFirst, networkFirst, cacheOnly, and networkOnly.
  • Added support for maxAge, refreshInBackground, and staleWhileRevalidate for finer cache control.
  • Integrated caching into the Stac widget when using Stac Cloud, including version-based updates for cached screens.
  • Added caching.mdx to document strategies, configuration, and behavior.

Motivation

  • Improve UX with instant loading for previously visited screens.
  • Provide offline and low-connectivity support for Stac-powered apps.

Related Issues

Closes #200

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Code refactor
  • Build configuration change
  • Documentation
  • Chore

Summary by CodeRabbit

  • New Features

    • Added caching & offline support with five strategies: optimistic, networkFirst, cacheFirst, cacheOnly, and networkOnly.
    • Configurable cache behavior including max age, background refresh, and stale-while-revalidate options.
    • New rendering methods: fromJson, fromAssets, fromNetwork.
    • Cache management functions to clear specific or all cached screens.
  • Documentation

    • Added comprehensive caching & offline support guide covering strategies, configuration, and best practices.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 25, 2025

Walkthrough

This PR introduces a comprehensive caching mechanism for Stac by adding configuration models, persistent storage service, five caching strategies (networkFirst, cacheFirst, optimistic, cacheOnly, networkOnly), integration into StacCloud fetch operations, public API extensions to the Stac widget, and documentation on caching behavior and configuration.

Changes

Cohort / File(s) Summary
Documentation
docs/concepts/caching.mdx, docs/docs.json
Added comprehensive caching documentation page describing five caching strategies, cache configuration properties, cache versioning, update flows, and practical code examples. Updated navigation to include new caching concepts page.
Cache Configuration & Storage Models
packages/stac/lib/src/models/stac_cache_config.dart, packages/stac/lib/src/models/stac_screen_cache.dart, packages/stac/lib/src/models/stac_screen_cache.g.dart
Introduced StacCacheStrategy enum with five strategies, StacCacheConfig class for configurable cache behavior (maxAge, refreshInBackground, staleWhileRevalidate), and StacScreenCache model for persisting cached UI structures with JSON serialization support.
Cache Service
packages/stac/lib/src/services/stac_cache_service.dart
Implemented StacCacheService with static methods for cache persistence using SharedPreferences: getCachedScreen, saveScreen, isCacheValid, removeScreen, and clearAllScreens.
Stac Framework Integration
packages/stac/lib/src/framework/stac.dart
Added cacheConfig parameter to Stac widget, introduced typedefs for custom error/loading widgets, added static methods (fromJson, fromAssets, fromNetwork, onCallFromJson), threaded cache configuration through rendering pipeline, and updated loading widget to use Material.
Cache & Service Exports
packages/stac/lib/src/models/models.dart, packages/stac/lib/src/services/services.dart, packages/stac/lib/stac.dart
Added exports for cache configuration, screen cache model, and cache service to public API surface.
Cloud Fetch Integration
packages/stac/lib/src/services/stac_cloud.dart
Integrated caching layer into fetchScreen with implementation of all five strategies (networkOnly, cacheOnly, networkFirst, cacheFirst, optimistic), background refresh logic, added public cache management methods (clearScreenCache, clearAllCache), and upgraded to HTTPS with Dio timeout configuration.
Platform Plugin Registration
examples/counter_example/macos/Flutter/GeneratedPluginRegistrant.swift, examples/movie_app/macos/Flutter/GeneratedPluginRegistrant.swift, examples/stac_gallery/macos/Flutter/GeneratedPluginRegistrant.swift
Registered SharedPreferencesPlugin in macOS plugin registry across all example apps.
Dependencies
packages/stac/pubspec.yaml
Added shared_preferences: ^2.5.3 dependency for local cache persistence.

Sequence Diagram

sequenceDiagram
    participant Stac
    participant StacCloud
    participant StacCacheService
    participant Network
    participant SharedPreferences

    Stac->>StacCloud: fetchScreen(routeName, cacheConfig)
    activate StacCloud
    
    alt optimistic (default)
        StacCloud->>StacCacheService: getCachedScreen(routeName)
        activate StacCacheService
        StacCacheService->>SharedPreferences: read cache entry
        deactivate StacCacheService
        
        alt cache exists
            StacCloud-->>Stac: return cached data immediately
            
            alt refreshInBackground enabled
                StacCloud->>Network: fetch in background (async)
                Network-->>StacCloud: new data
                StacCloud->>StacCacheService: saveScreen (if version newer)
            end
        else cache missing
            StacCloud->>Network: fetch from network
            Network-->>StacCloud: response
            StacCloud->>StacCacheService: saveScreen(screenName, stacJson, version)
            StacCacheService->>SharedPreferences: store cache entry
            StacCloud-->>Stac: return fetched data
        end
    else cacheFirst
        StacCloud->>StacCacheService: isCacheValid(screenName, maxAge)
        alt valid cache exists
            StacCloud->>StacCacheService: getCachedScreen(routeName)
            StacCloud-->>Stac: return cached data
        else cache missing or expired
            StacCloud->>Network: fetch from network
            Network-->>StacCloud: response
            StacCloud->>StacCacheService: saveScreen()
            StacCloud-->>Stac: return fetched data
        end
    else networkFirst
        StacCloud->>Network: try network first
        alt success
            Network-->>StacCloud: response
            StacCloud->>StacCacheService: saveScreen()
            StacCloud-->>Stac: return network data
        else failure
            StacCloud->>StacCacheService: getCachedScreen(routeName)
            StacCloud-->>Stac: return cached data (fallback)
        end
    end
    deactivate StacCloud
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • packages/stac/lib/src/services/stac_cloud.dart — Dense caching strategy implementations (five distinct paths), background fetch coordination, version-based invalidation logic, and error handling across network/cache flows
  • packages/stac/lib/src/framework/stac.dart — Public API surface expansion (multiple new typedefs and static methods), rendering pipeline modifications threading cacheConfig through multiple layers
  • packages/stac/lib/src/services/stac_cache_service.dart — Static service pattern, SharedPreferences interaction, error suppression strategy (returning null/false instead of throwing), and concurrent clearAllScreens implementation
  • Integration consistency: Verify cacheConfig threading from Stac widget through _StacView to StacCloud.fetchScreen, and that default values align across all integration points

Suggested reviewers

  • Potatomonsta
  • rahulbisht25

Poem

🐰 Hops through the cache with glee!
Five strategies stored for thee—
Optimistic dreams with background refresh,
Offline dreams stay fresh and fresh!
Version checks keep data spry,
Stale-while-revalidate won't let it die! 🥬✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Out of Scope Changes check ❓ Inconclusive Most changes directly support caching implementation, but three macOS GeneratedPluginRegistrant.swift files appear tangential to core caching feature; unclear whether shared_preferences dependency integration required these iOS plugin registration changes. Clarify whether shared_preferences plugin registration in example apps' macOS files is a required dependency of the caching implementation or an incidental change.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add Screen Caching & Offline Support to Stac' clearly and concisely summarizes the main feature addition, is specific about what changed (caching and offline support), and is directly related to the changeset content.
Linked Issues check ✅ Passed All coding requirements from issue #200 are met: caching areas identified (Stac Cloud screen fetching), caching strategy implemented (StacCacheConfig with five strategies), cache invalidation handled (version-based updates, maxAge, removeScreen), and configuration options provided (StacCacheConfig parameters).
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dv/caching

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
packages/stac/lib/src/models/stac_cache_config.dart (1)

1-22: Clarify strategy docs and consider allowing maxAge to be cleared in copyWith

Overall the enum and config API look good and read well. Two small polish suggestions:

  • The networkFirst doc reads as “Always fetch from network, update cache in background.” If the actual behavior is “try network, then fall back to cache on failure/offline”, it may be worth making that explicit to distinguish it from networkOnly and avoid confusion for users familiar with common “network first” semantics.
  • In copyWith, maxAge uses maxAge ?? this.maxAge, which means there’s no way to intentionally reset maxAge back to null via copyWith. If you expect callers to sometimes remove a time‑based expiry and fall back to “version only” invalidation, consider the usual sentinel approach (e.g., Object? maxAge = _sentinel) so copyWith(maxAge: null) can clear it.

Neither is blocking, but adjusting them would make the API more predictable for consumers.

Also applies to: 48-92

packages/stac/lib/src/models/models.dart (1)

1-2: Barrel exports expose caching models as part of the public API

Re‑exporting StacCacheConfig and StacScreenCache through models.dart aligns with the goal of making caching configurable from user code. Just ensure you’re comfortable with StacScreenCache being a stable public type (vs. an internal detail), since changing it later would be a breaking change.

docs/concepts/caching.mdx (1)

214-220: Clarify the "Initial Load" column for accuracy.

The strategy comparison table for optimistic shows "Network → Cache" for initial load, which could be misinterpreted. On first load (no cache), it fetches from network. On subsequent loads with valid/stale cache, it returns cache immediately and fetches in background.

Consider clarifying:

-| `optimistic` | Network → Cache | Cache (bg update) | ✅ Yes | Fast UX |
+| `optimistic` | Network (then cache) | Cache + bg update | ✅ Yes | Fast UX |

This better conveys that the initial load is network-bound, while subsequent loads serve cache immediately.

packages/stac/lib/src/services/stac_cache_service.dart (1)

17-19: Minor race condition in SharedPreferences initialization.

If multiple async calls invoke _sharedPrefs before _prefs is assigned, each will call SharedPreferences.getInstance(). While benign (SharedPreferences returns the same singleton), it's slightly wasteful.

Consider a Completer-based approach for cleaner single initialization:

static Completer<SharedPreferences>? _prefsCompleter;

static Future<SharedPreferences> get _sharedPrefs async {
  if (_prefsCompleter == null) {
    _prefsCompleter = Completer<SharedPreferences>();
    _prefsCompleter!.complete(SharedPreferences.getInstance());
  }
  return _prefsCompleter!.future;
}
packages/stac/lib/src/services/stac_cloud.dart (1)

92-96: Remove unreachable switch cases.

The cacheOnly and networkOnly cases in the switch statement are unreachable since they're handled by early returns at lines 48-63. This is dead code that can be removed for clarity.

      case StacCacheStrategy.optimistic:
        return _handleOptimistic(
          routeName,
          cachedScreen,
          isCacheValid,
          cacheConfig,
        );
-
-      case StacCacheStrategy.cacheOnly:
-      case StacCacheStrategy.networkOnly:
-        // Already handled above
-        return _fetchFromNetwork(routeName, saveToCache: false);
    }
  }

Since the switch is on an enum and all cases must be handled, you'll need to keep the cases but can simplify by asserting they're unreachable or relying on the enum exhaustiveness.

packages/stac/lib/src/framework/stac.dart (1)

376-379: Consider defensive null handling for response data.

While snapshot.hasData ensures snapshot.data is non-null, accessing data['stacJson'] assumes a specific response structure. If the API response format changes or is malformed, this will throw a runtime exception.

Consider adding defensive checks:

if (snapshot.hasData) {
  final responseData = snapshot.data!.data;
  final jsonString = responseData?['stacJson'] as String?;
  if (jsonString == null) {
    return errorWidget ?? const SizedBox();
  }
  return StacService.fromJson(jsonDecode(jsonString), context) ??
      const SizedBox();
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24bcb68 and 1542cfa.

⛔ Files ignored due to path filters (4)
  • examples/counter_example/pubspec.lock is excluded by !**/*.lock
  • examples/movie_app/ios/Podfile.lock is excluded by !**/*.lock
  • examples/movie_app/pubspec.lock is excluded by !**/*.lock
  • examples/stac_gallery/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • docs/concepts/caching.mdx (1 hunks)
  • docs/docs.json (1 hunks)
  • examples/counter_example/macos/Flutter/GeneratedPluginRegistrant.swift (1 hunks)
  • examples/movie_app/macos/Flutter/GeneratedPluginRegistrant.swift (1 hunks)
  • examples/stac_gallery/macos/Flutter/GeneratedPluginRegistrant.swift (1 hunks)
  • packages/stac/lib/src/framework/stac.dart (8 hunks)
  • packages/stac/lib/src/models/models.dart (1 hunks)
  • packages/stac/lib/src/models/stac_cache_config.dart (1 hunks)
  • packages/stac/lib/src/models/stac_screen_cache.dart (1 hunks)
  • packages/stac/lib/src/models/stac_screen_cache.g.dart (1 hunks)
  • packages/stac/lib/src/services/services.dart (1 hunks)
  • packages/stac/lib/src/services/stac_cache_service.dart (1 hunks)
  • packages/stac/lib/src/services/stac_cloud.dart (2 hunks)
  • packages/stac/lib/stac.dart (1 hunks)
  • packages/stac/pubspec.yaml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
examples/stac_gallery/macos/Flutter/GeneratedPluginRegistrant.swift (2)
examples/counter_example/macos/Flutter/GeneratedPluginRegistrant.swift (1)
  • RegisterGeneratedPlugins (12-16)
examples/movie_app/macos/Flutter/GeneratedPluginRegistrant.swift (1)
  • RegisterGeneratedPlugins (12-16)
examples/movie_app/macos/Flutter/GeneratedPluginRegistrant.swift (2)
examples/counter_example/macos/Flutter/GeneratedPluginRegistrant.swift (1)
  • RegisterGeneratedPlugins (12-16)
examples/stac_gallery/macos/Flutter/GeneratedPluginRegistrant.swift (1)
  • RegisterGeneratedPlugins (13-18)
🪛 LanguageTool
docs/concepts/caching.mdx

[style] ~104-~104: In American English, “you” and “have” do not usually form a contraction unless they’re followed by a past participle.
Context: ... Offline-only mode, airplane mode, when you've pre-cached screens. ### 5. Network Onl...

(IVE_I_HAVE_AMERICAN_STYLE)

🔇 Additional comments (18)
packages/stac/pubspec.yaml (1)

17-27: SharedPreferences dependency looks appropriate; just confirm SDK / platform matrix

Adding shared_preferences: ^2.5.3 fits the new caching service; please just double‑check this version is compatible with your declared Flutter/Dart SDKs and all platforms you intend to support (especially web/desktop), since this package wires in platform plugins.

examples/movie_app/macos/Flutter/GeneratedPluginRegistrant.swift (1)

8-15: MacOS SharedPreferences plugin registration is consistent

Importing shared_preferences_foundation and registering SharedPreferencesPlugin alongside the other plugins matches the pattern in the other example apps; no issues from a wiring perspective. Just ensure these registrant files stay in sync with Flutter’s generated output after future plugin changes.

examples/counter_example/macos/Flutter/GeneratedPluginRegistrant.swift (1)

8-15: Counter example macOS registrant wiring looks good

The added shared_preferences_foundation import and SharedPreferencesPlugin registration align with the other examples and the new shared_preferences dependency; nothing to change here.

examples/stac_gallery/macos/Flutter/GeneratedPluginRegistrant.swift (1)

8-17: Gallery macOS registrant correctly adds SharedPreferences

The shared_preferences foundation import and registration are added without disturbing existing plugins (including WebView); this is consistent with the other macOS examples.

packages/stac/lib/src/models/stac_screen_cache.g.dart (1)

1-23: Generated JSON mapping for StacScreenCache looks correct

The (de)serialization functions cleanly mirror the expected fields (name, stacJson, version as int, cachedAt as ISO 8601). This is fine as long as all writers use ISO 8601 strings for cachedAt.

docs/docs.json (1)

37-44: New concepts/caching docs entry is wired correctly

The new navigation entry under “Concepts” looks consistent with the existing structure. Just confirm that docs/concepts/caching.mdx (or equivalent) exists and uses the same slug so the link doesn’t 404.

packages/stac/lib/stac.dart (1)

2-2: LGTM!

The new export for models.dart correctly exposes the caching configuration types (StacCacheConfig, StacCacheStrategy, StacScreenCache) through the public API, enabling consumers to configure caching behavior.

packages/stac/lib/src/services/services.dart (1)

1-2: LGTM!

The cache service export follows the existing barrel pattern and correctly exposes StacCacheService for public use.

docs/concepts/caching.mdx (1)

1-232: Well-documented caching feature.

The documentation comprehensively covers all cache strategies, configuration options, and usage patterns with clear examples. The structure with behavior summaries and "Best for" recommendations is helpful for users choosing the right strategy.

packages/stac/lib/src/models/stac_screen_cache.dart (1)

1-86: LGTM!

The StacScreenCache model is well-designed:

  • Immutable with final fields and const constructor
  • Complete JSON serialization with both map and string variants
  • Proper equality semantics using all fields
  • copyWith for immutable updates

The lack of error handling in fromJsonString is acceptable since StacCacheService.getCachedScreen wraps calls in try-catch.

packages/stac/lib/src/services/stac_cache_service.dart (2)

24-40: LGTM - Good defensive error handling.

The getCachedScreen method properly catches exceptions and returns null, allowing the app to gracefully fall back to network fetching. This resilience pattern is consistently applied throughout the service.


109-122: Nice use of parallel deletion.

Using Future.wait for clearing cache entries in parallel is more efficient than sequential awaits, especially when there are many cached screens.

packages/stac/lib/src/services/stac_cloud.dart (3)

221-255: Good background fetch implementation.

The _fetchAndUpdateInBackground method has solid design:

  • Deduplication via _backgroundFetchInProgress Set prevents parallel fetches for the same screen
  • Version comparison ensures cache is only updated when server has newer content
  • finally block guarantees cleanup even on errors
  • Silent failure is appropriate for background operations

15-22: Good network configuration.

The Dio configuration with explicit timeouts (10s connect, 30s receive) and HTTPS URL provides reasonable defaults for mobile network conditions.


147-165: Verify optimistic strategy behavior aligns with documentation.

The _handleOptimistic method returns cache if it exists and is either valid OR staleWhileRevalidate is true. However, the default StacCacheConfig has staleWhileRevalidate: false, so expired cache won't be returned by default.

This means with default config:

  • Valid cache → return immediately, background refresh
  • Expired cache → fetch from network (not instant)

Confirm this matches the documented behavior: "Returns cached data instantly (even if expired)" at line 35 of caching.mdx. The "even if expired" claim is only true if staleWhileRevalidate: true.

packages/stac/lib/src/framework/stac.dart (3)

110-118: LGTM - Consistent default cache configuration.

The default cacheConfig using StacCacheStrategy.optimistic matches the default in StacCloud.fetchScreen and aligns with the PR objective of providing fast perceived performance with instant loading.


387-394: Good use of Material wrapper.

Wrapping CircularProgressIndicator in Material ensures proper theme inheritance and avoids rendering issues when the widget tree lacks a Material ancestor.


42-99: Excellent class documentation.

The comprehensive documentation with usage examples, caching explanation, and references to related types significantly improves developer experience. This is a great model for API documentation.

@divyanshub024 divyanshub024 merged commit a028171 into dev Nov 27, 2025
6 checks passed
@divyanshub024 divyanshub024 deleted the dv/caching branch November 27, 2025 09:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add Caching Mechanism

3 participants