Skip to content

Conversation

@Potatomonsta
Copy link
Contributor

@Potatomonsta Potatomonsta commented Dec 11, 2025

Description

This PR adds comprehensive theme support to the Stac framework, enabling themes to be loaded from Stac Cloud similar to how screens are currently loaded. The implementation includes async theme loading, intelligent caching, and proper context handling.

Key Changes

New Features:

  • Added StacArtifactType enum to support both screens and themes as fetchable artifacts
  • Added StacCloudTheme annotation for marking theme builder methods
  • Added StacAppTheme helper class with methods to load themes from:
    • Stac Cloud (fromCloud)
    • Network requests (fromNetwork)
    • Raw JSON (fromJson)
  • Extended StacApp to support FutureOr<StacTheme?> for both theme and darkTheme parameters, enabling async theme loading
  • Added loading state handling with _ThemeFutureLoading widget when themes are being fetched

Refactoring:

  • Refactored StacCacheService to be artifact-type agnostic:
    • Renamed methods from screen-specific to artifact-specific (getCachedArtifact, saveArtifact, etc.)
    • Added artifact type parameter to all cache operations
    • Supports both 'screen' and 'theme' artifact types
  • Refactored StacCloud to support fetching both screens and themes:
    • Unified fetching logic in _fetchArtifact method
    • Added fetchTheme method as public API
    • Updated background fetch tracking to support multiple artifact types
    • Added theme-specific cache management methods (clearThemeCache, clearAllThemeCache)

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

Release Notes

  • New Features
    • Added support for asynchronous theme loading from cloud and network sources
    • Themes can now be fetched dynamically at runtime and displayed during loading
    • Extended artifact support to include themes alongside existing functionality
    • Added new annotation for marking theme definitions

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

Add comprehensive theme support to the Stac framework, enabling themes to be loaded from Stac Cloud similar to screens. This includes:

- Add StacArtifactType enum to support both screens and themes
- Add StacCloudTheme annotation for marking theme builder methods
- Add StacAppTheme helper class for loading themes from cloud, network, or JSON
- Extend StacApp to support FutureOr<StacTheme?> for async theme loading
- Refactor StacCacheService to be artifact-type agnostic (supports screens and themes)
- Refactor StacCloud to support fetching both screens and themes with unified caching

Themes can now be loaded asynchronously from Stac Cloud using the same caching strategies as screens (optimistic, cache-first, network-first, etc.), with proper loading states handled in StacApp.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

Walkthrough

This PR introduces asynchronous theme loading and resolution for the Stac framework. It generalizes the caching and cloud-fetch system from screen-only artifacts to support multiple types (screens, themes), adds a new StacAppTheme utility class for loading themes from cloud/network/JSON, and exports a new StacCloudTheme annotation for marking theme definition methods.

Changes

Cohort / File(s) Summary
Theme Loading & Public API Expansion
packages/stac/lib/src/framework/framework.dart, packages/stac/lib/src/framework/stac_app_theme.dart
New StacAppTheme utility class providing fromCloud, fromNetwork, and fromJson static methods to load StacTheme definitions. Exports stac_app_theme.dart to public API.
Async Theme Resolution in StacApp
packages/stac/lib/src/framework/stac_app.dart
Replaced simple theme fields with FutureOr-wrapped inputs; added internal _resolveThemes, _withResolvedThemes, and _ResolvedStacThemes for async theme resolution; introduced _ThemeFutureLoading widget for loading state during resolution.
Artifact Type Classification
packages/stac/lib/src/models/stac_artifact_type.dart
New enum StacArtifactType with screen and theme variants to classify artifacts fetched from Stac Cloud.
Generalized Cache & Cloud Services
packages/stac/lib/src/services/stac_cache_service.dart, packages/stac/lib/src/services/stac_cloud.dart
Generalized cache handling from screen-only to multiple artifact types via artifactType parameter; renamed methods (getCachedScreen → getCachedArtifact, saveScreen → saveArtifact, etc.); added isCacheValid and isCacheValidSync; reworked stac_cloud.dart with artifact-type aware fetch routing, added fetchTheme method alongside fetchScreen, and introduced clearThemeCache/clearAllThemeCache utilities.
Theme Annotations
packages/stac_core/lib/annotations/annotations.dart, packages/stac_core/lib/annotations/stac_cloud_theme.dart
New StacCloudTheme annotation class with required themeName parameter to mark methods returning theme definitions; exported from annotations barrel.

Sequence Diagram(s)

sequenceDiagram
    participant App as StacApp
    participant Builder as _withResolvedThemes
    participant Resolver as _resolveThemes
    participant Theme as StacAppTheme
    participant Network as Network/Cloud
    participant Material as MaterialApp
    
    App->>Builder: build with FutureOr themes
    Builder->>Resolver: resolve theme & darkTheme
    Resolver->>Theme: theme.runtimeType check
    alt theme is Future
        Theme->>Network: fetch theme (async)
        Network-->>Theme: StacTheme
    else theme is StacTheme
        Theme-->>Resolver: immediate value
    end
    Theme-->>Resolver: resolved theme
    Resolver-->>Builder: _ResolvedStacThemes
    Builder->>Builder: await resolution via FutureBuilder
    opt still loading
        Builder->>App: show _ThemeFutureLoading
    end
    Builder->>Material: MaterialApp with parsed themes
    Material-->>App: rendered app
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • stac_app.dart: Async theme resolution with FutureOr, new internal lifecycle methods, and FutureBuilder integration require careful validation of state handling and loading states.
  • stac_cache_service.dart & stac_cloud.dart: Generalization of caching/cloud logic across artifact types involves multiple renamed methods and refactored helper signatures; verify artifact type parameter consistency throughout.
  • stac_app_theme.dart: Multiple async entry points (fromCloud, fromNetwork) with JSON normalization logic; validate error handling and null propagation.
  • Watch for method signature consistency: getCachedArtifact vs saveArtifact parameter ordering and artifact type usage across files.

Possibly related PRs

Suggested reviewers

  • divyanshub024

Poem

🐰 Themes now flow like streams so free,
Async, cached, for all to see!
From cloud to app, they gently load,
While artifacts skip down the road—
Screens and themes, side by side,
In generalized cache they hide! 🎨

Pre-merge checks and finishing touches

✅ 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 accurately describes the main feature addition: theme support to the Stac framework with cloud loading capability, which aligns with the PR's core objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mn/themeing

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: 5

🧹 Nitpick comments (9)
packages/stac/lib/src/framework/stac_app.dart (2)

270-280: Consider resolving theme futures in parallel.

Both theme futures are awaited sequentially. Using Future.wait would allow parallel resolution.

     return Future<_ResolvedStacThemes>(() async {
-      final resolvedTheme =
-          await (themeFuture ?? Future<StacTheme?>.value(themeValue));
-      final resolvedDarkTheme =
-          await (darkThemeFuture ?? Future<StacTheme?>.value(darkThemeValue));
+      final results = await Future.wait([
+        themeFuture ?? Future<StacTheme?>.value(themeValue),
+        darkThemeFuture ?? Future<StacTheme?>.value(darkThemeValue),
+      ]);
 
       return _ResolvedStacThemes(
-        theme: resolvedTheme,
-        darkTheme: resolvedDarkTheme,
+        theme: results[0],
+        darkTheme: results[1],
       );
     });

317-324: Loading widget has no theme context.

This widget renders before MaterialApp is built, so there's no inherited theme. The Material widget will use fallback colors which may cause a visual flash when the app loads. Consider specifying explicit colors.

   @override
   Widget build(BuildContext context) {
-    return const Material(child: Center(child: CircularProgressIndicator()));
+    return const ColoredBox(
+      color: Colors.white, // Or Colors.transparent for less jarring transition
+      child: Center(child: CircularProgressIndicator()),
+    );
   }
packages/stac/lib/src/framework/stac_app_theme.dart (1)

16-33: Silent null returns may hinder debugging.

Multiple failure paths return null without any indication of what went wrong. Consider adding debug logging to help diagnose theme loading issues in development.

   static Future<StacTheme?> fromCloud({required String themeName}) async {
     final response = await StacCloud.fetchTheme(themeName: themeName);
     if (response == null) {
+      assert(() { debugPrint('StacAppTheme.fromCloud: fetchTheme returned null for "$themeName"'); return true; }());
       return null;
     }

     final rawData = response.data;
     if (rawData is! Map<String, dynamic>) {
+      assert(() { debugPrint('StacAppTheme.fromCloud: response.data is not Map<String, dynamic>'); return true; }());
       return null;
     }
packages/stac/lib/src/services/stac_cache_service.dart (2)

19-29: Use StacArtifactType enum instead of string for type safety.

The artifactType parameter accepts any string, but only 'screen' and 'theme' are valid. Using the StacArtifactType enum would provide compile-time safety and prevent runtime ArgumentError.

-  static String _getCachePrefix(String artifactType) {
-    switch (artifactType) {
-      case 'screen':
+  static String _getCachePrefix(StacArtifactType artifactType) {
+    switch (artifactType) {
+      case StacArtifactType.screen:
         return 'stac_screen_cache_';
-      case 'theme':
+      case StacArtifactType.theme:
         return 'stac_theme_cache_';
-      default:
-        throw ArgumentError('Unknown artifact type: $artifactType');
     }
   }

This would require updating all method signatures to accept StacArtifactType instead of String.


48-48: StacScreenCache class used for theme artifacts.

The StacScreenCache class is now used to cache both screens and themes, which is a naming inconsistency. Consider renaming to StacArtifactCache for clarity, or document that the class is intentionally reused.

Also applies to: 68-73

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

1-8: Inconsistent artifact type handling between StacCloud and StacCacheService.

The StacArtifactType enum is actively used throughout StacCloud (via switch statements and type-safe parameters), but when StacCloud calls StacCacheService methods (e.g., clearScreenCache, clearThemeCache), it passes hardcoded strings ('screen', 'theme') instead of enum values. Meanwhile, StacCacheService._getCachePrefix() switches on these string literals. Consider having StacCacheService accept StacArtifactType instead of String to maintain type consistency and avoid string literal brittleness at the boundary.

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

69-150: Tighten strategy handling in _fetchArtifact to avoid confusing dead branch

The overall flow (early handling of networkOnly / cacheOnly, then switching on the remaining strategies) is good, but the final cacheOnly/networkOnly cases in the switch are now unreachable and contradict the comment:

case StacCacheStrategy.cacheOnly:
case StacCacheStrategy.networkOnly:
  // Already handled above
  return _fetchArtifactFromNetwork(
    artifactType: artifactType,
    artifactName: artifactName,
    saveToCache: false,
  );

Because both strategies are already fully handled by the if branches above (return/throw), this code will never run, but if someone refactors out the early ifs later it would silently change cacheOnly to “network-only, no cache”.

Consider either:

  • Moving networkOnly/cacheOnly handling entirely into the switch and dropping the early ifs, or
  • Keeping the early ifs and turning these two cases into an explicit unreachable/StateError so any future misuse is caught quickly.

For example:

       case StacCacheStrategy.optimistic:
         return _handleArtifactOptimistic(
           artifactType: artifactType,
           artifactName: artifactName,
           cachedArtifact: cachedArtifact,
           isCacheValid: isCacheValid,
           config: cacheConfig,
         );
 
       case StacCacheStrategy.cacheOnly:
       case StacCacheStrategy.networkOnly:
-        // Already handled above
-        return _fetchArtifactFromNetwork(
-          artifactType: artifactType,
-          artifactName: artifactName,
-          saveToCache: false,
-        );
+        // These should have been handled by the early returns above.
+        throw StateError(
+          'Unexpected strategy ${cacheConfig.strategy} in _fetchArtifact; '
+          'cacheOnly/networkOnly should be handled before the switch.',
+        );

Functionally nothing is broken today, but this makes the intent clearer and safer for future changes.


174-266: Strategy helpers read correctly and match the intended semantics

_handleArtifactNetworkFirst, _handleArtifactCacheFirst, and _handleArtifactOptimistic correctly implement the advertised behaviors (network-first with cache fallback, cache-first with optional background refresh and stale-while-revalidate, and optimistic with background update). Logging with artifactType.name is helpful for debugging.

The only (very minor) nit is that StacScreenCache is now effectively an artifact cache model for both screens and themes; if you ever generalize more artifact types, you might consider renaming it to something like StacArtifactCache for clarity, but that would be a larger cross-file change.


381-421: Theme fetch/clear helpers mirror screen APIs; consider removing hard-coded type strings

The new fetchTheme, clearThemeCache, and clearAllThemeCache helpers align well with the existing screen APIs and the artifact-type abstraction.

For the cache-clear methods, you’re still using string literals 'screen' / 'theme':

static Future<bool> clearScreenCache(String routeName) {
  return StacCacheService.removeArtifact(routeName, 'screen');
}

static Future<bool> clearAllCache() {
  return StacCacheService.clearAllArtifacts('screen');
}

static Future<bool> clearThemeCache(String themeName) {
  return StacCacheService.removeArtifact(themeName, 'theme');
}

static Future<bool> clearAllThemeCache() {
  return StacCacheService.clearAllArtifacts('theme');
}

Since you already have _getArtifactTypeString, you can avoid duplicating these strings and keep everything enum-driven:

 static Future<bool> clearScreenCache(String routeName) {
-  return StacCacheService.removeArtifact(routeName, 'screen');
+  return StacCacheService.removeArtifact(
+    routeName,
+    _getArtifactTypeString(StacArtifactType.screen),
+  );
 }
 
 static Future<bool> clearAllCache() {
-  return StacCacheService.clearAllArtifacts('screen');
+  return StacCacheService.clearAllArtifacts(
+    _getArtifactTypeString(StacArtifactType.screen),
+  );
 }
 
 static Future<bool> clearThemeCache(String themeName) {
-  return StacCacheService.removeArtifact(themeName, 'theme');
+  return StacCacheService.removeArtifact(
+    themeName,
+    _getArtifactTypeString(StacArtifactType.theme),
+  );
 }
 
 static Future<bool> clearAllThemeCache() {
-  return StacCacheService.clearAllArtifacts('theme');
+  return StacCacheService.clearAllArtifacts(
+    _getArtifactTypeString(StacArtifactType.theme),
+  );
 }

This keeps the cache-layer contract tied to the enum and avoids subtle drift if the underlying type strings ever change.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4ebbca and 43ffe4f.

📒 Files selected for processing (8)
  • packages/stac/lib/src/framework/framework.dart (1 hunks)
  • packages/stac/lib/src/framework/stac_app.dart (7 hunks)
  • packages/stac/lib/src/framework/stac_app_theme.dart (1 hunks)
  • packages/stac/lib/src/models/stac_artifact_type.dart (1 hunks)
  • packages/stac/lib/src/services/stac_cache_service.dart (4 hunks)
  • packages/stac/lib/src/services/stac_cloud.dart (7 hunks)
  • packages/stac_core/lib/annotations/annotations.dart (1 hunks)
  • packages/stac_core/lib/annotations/stac_cloud_theme.dart (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: analyze
🔇 Additional comments (9)
packages/stac_core/lib/annotations/annotations.dart (1)

3-4: New stac_cloud_theme.dart export looks consistent

Exporting the new annotation alongside stac_screen.dart keeps the annotations barrel file up to date and is aligned with how other annotations are exposed.

packages/stac_core/lib/annotations/stac_cloud_theme.dart (1)

13-18: Annotation shape is simple and appropriate for intended use

Const constructor with a required themeName and a single final field is a clean, idiomatic way to model this annotation and should work well for discovery/registration of theme builders.

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

113-114: Good backward-compatible API change.

The change from StacTheme? to FutureOr<StacTheme?>? maintains backward compatibility - existing synchronous theme assignments continue to work while enabling async loading.

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

5-5: LGTM!

Export added to expose the new StacAppTheme utility class through the framework's public API surface.

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

39-49: LGTM!

The fromNetwork method correctly delegates to StacNetworkService.request and handles null responses gracefully. The BuildContext usage is appropriate as it's passed directly to the service.

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

130-139: LGTM!

The cache validity logic is clear and handles edge cases properly - null artifact returns false, null maxAge means no expiration. The age comparison is straightforward.


97-110: LGTM!

Good use of Future.wait for parallel key removal, and prefix-based filtering correctly isolates artifacts by type.

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

26-52: Helper extraction for artifact URLs and params looks solid

Centralizing _baseUrl, _getFetchUrl, _getQueryParamName, and _getArtifactTypeString keeps screen/theme handling consistent and makes it easy to extend to new artifact types. No changes requested here.


268-331: Network + cache integration for artifacts looks consistent

_makeArtifactRequest, _fetchArtifactFromNetwork, and _buildArtifactCacheResponse nicely generalize the old screen-only path:

  • Request URL and query param are derived from the artifact type.
  • Cache read/write uses _getArtifactTypeString(artifactType) consistently, so screens and themes share the same caching mechanism with type partitioning.
  • Cached responses are shaped identically to network ones (name, stacJson, version), which keeps downstream consumers simple.

No issues from a correctness standpoint; just ensure the Cloud endpoints for both screens and themes always return that { name, stacJson, version } shape so caching works uniformly.

Comment on lines +6 to +12
/// Example usage:
/// ```dart
/// @StacThemeAnnotation(themeName: 'darkTheme')
/// ThemeData buildDarkTheme() {
/// return ThemeData.dark();
/// }
/// ```
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Doc example uses the wrong annotation name

The example shows @StacThemeAnnotation but this file defines StacCloudTheme. That mismatch can confuse users and docs/tools.

Consider updating the example to use the actual annotation:

-/// @StacThemeAnnotation(themeName: 'darkTheme')
+/// @StacCloudTheme(themeName: 'darkTheme')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Example usage:
/// ```dart
/// @StacThemeAnnotation(themeName: 'darkTheme')
/// ThemeData buildDarkTheme() {
/// return ThemeData.dark();
/// }
/// ```
/// Example usage:
///
🤖 Prompt for AI Agents
In packages/stac_core/lib/annotations/stac_cloud_theme.dart around lines 6 to
12, the documentation example uses the wrong annotation name
(`@StacThemeAnnotation`) which doesn't match the defined annotation
`StacCloudTheme`; update the example to use the actual annotation (e.g.,
`@StacCloudTheme(themeName: 'darkTheme')`) and ensure the annotated function
signature in the example matches the library's expected usage so docs and tools
are consistent.

Comment on lines +73 to +78
if (payload is String) {
final decoded = jsonDecode(payload);
if (decoded is Map<String, dynamic>) {
return decoded;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

jsonDecode can throw FormatException on invalid JSON.

If payload is a malformed JSON string, jsonDecode throws an exception that isn't caught, propagating up to the caller unexpectedly.

     if (payload is String) {
-      final decoded = jsonDecode(payload);
-      if (decoded is Map<String, dynamic>) {
-        return decoded;
+      try {
+        final decoded = jsonDecode(payload);
+        if (decoded is Map<String, dynamic>) {
+          return decoded;
+        }
+      } on FormatException {
+        return null;
       }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (payload is String) {
final decoded = jsonDecode(payload);
if (decoded is Map<String, dynamic>) {
return decoded;
}
}
if (payload is String) {
try {
final decoded = jsonDecode(payload);
if (decoded is Map<String, dynamic>) {
return decoded;
}
} on FormatException {
return null;
}
}
🤖 Prompt for AI Agents
In packages/stac/lib/src/framework/stac_app_theme.dart around lines 73 to 78,
jsonDecode(payload) can throw a FormatException for malformed JSON; wrap the
jsonDecode call in a try/catch that catches FormatException (and optionally any
other parsing errors), handle the error by returning null (or the function's
appropriate fallback) and/or log the parse failure so the exception doesn't
propagate; ensure existing behavior still returns the decoded Map when parsing
succeeds.

Comment on lines +283 to +307
Widget _withResolvedThemes(
BuildContext context,
Widget Function(BuildContext, _ResolvedStacThemes) builder,
) {
final resolved = _resolveThemes();
if (resolved is Future<_ResolvedStacThemes>) {
return FutureBuilder<_ResolvedStacThemes>(
future: resolved,
builder: (futureContext, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const _ThemeFutureLoading();
}
if (snapshot.hasError) {
return const _ThemeFutureLoading();
}
final themes = snapshot.data;
if (themes == null) {
return const _ThemeFutureLoading();
}
return builder(futureContext, themes);
},
);
}
return builder(context, resolved);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Future is recreated on every widget rebuild.

_resolveThemes() is called inside _withResolvedThemes during build(). When theme or darkTheme is a Future, a new Future object is created on each rebuild. FutureBuilder uses reference equality, so it will restart the async operation and show the loading state again whenever StacApp rebuilds.

Consider caching the resolved future in a stateful widget or using AsyncSnapshot's connectionState more carefully, or lift the resolution to an ancestor widget/state.

-class StacApp extends StatelessWidget {
+class StacApp extends StatefulWidget {
   const StacApp({
     ...
   });
+
+  @override
+  State<StacApp> createState() => _StacAppState();
+}
+
+class _StacAppState extends State<StacApp> {
+  late final FutureOr<_ResolvedStacThemes> _resolvedThemes;
+
+  @override
+  void initState() {
+    super.initState();
+    _resolvedThemes = _resolveThemes();
+  }
+
+  // Move build logic here, using _resolvedThemes instead of calling _resolveThemes()

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +295 to +300
if (snapshot.hasError) {
return const _ThemeFutureLoading();
}
final themes = snapshot.data;
if (themes == null) {
return const _ThemeFutureLoading();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Error state is silently swallowed with no feedback.

When theme loading fails, the same loading indicator is shown indefinitely. Consider logging the error and/or falling back to a default theme rather than showing a perpetual loading state.

           if (snapshot.hasError) {
+            // Log error for debugging
+            debugPrint('Theme loading failed: ${snapshot.error}');
-            return const _ThemeFutureLoading();
+            // Fall back to no theme rather than infinite loading
+            return builder(futureContext, const _ResolvedStacThemes(theme: null, darkTheme: null));
           }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/stac/lib/src/framework/stac_app.dart around lines 295-300 the
snapshot.hasError branch currently returns the same loading widget and swallows
the error; change this to log the snapshot.error (using debugPrint or the app
logger) and return a sensible fallback (for example a default ThemeData-based
widget or an error/fallback _Theme widget) instead of _ThemeFutureLoading so the
app doesn't show a perpetual spinner; ensure snapshot.error and
snapshot.stackTrace are included in the log and that the fallback provides safe
default colors/fonts so UI remains usable.

Comment on lines +54 to 58
StacArtifactType.theme: {},
};

/// Fetches an artifact from Stac Cloud with intelligent caching.
///
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix _backgroundFetchInProgress value literals (currently map literals, not sets)

Right now the values in _backgroundFetchInProgress use {}, which is a Map literal in Dart, but the field type is Map<StacArtifactType, Set<String>>. This won’t compile:

static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress = {
  StacArtifactType.screen: {},
  StacArtifactType.theme: {},
};

You need set literals here:

-  static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress = {
-    StacArtifactType.screen: {},
-    StacArtifactType.theme: {},
-  };
+  static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress = {
+    StacArtifactType.screen: <String>{},
+    StacArtifactType.theme: <String>{},
+  };

Given _fetchAndUpdateArtifactInBackground does _backgroundFetchInProgress[artifactType]!, any missing key will also throw at runtime; if you expect more artifact types later, consider building this map from StacArtifactType.values to stay future-proof.

Also applies to: 337-378

🤖 Prompt for AI Agents
packages/stac/lib/src/services/stac_cloud.dart lines 54-58 (and similarly lines
337-378): the map is typed as Map<StacArtifactType, Set<String>> but uses {}
which creates Map literals and not Set<String> — replace the value literals with
explicit set literals like <String>{} (e.g. StacArtifactType.screen: <String>{},
StacArtifactType.theme: <String>{}); to be future-proof you can build the map
from StacArtifactType.values (e.g.
Map.fromEntries(StacArtifactType.values.map((t) => MapEntry(t, <String>{})))) or
ensure every enum key is initialized so
_backgroundFetchInProgress[artifactType]! never returns null (alternatively use
putIfAbsent before accessing).

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.

2 participants