Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,15 @@ class CdnDefinitionProvider extends DefinitionProvider {
}
}

/// Whether a CDN artifact refresh should update appBundle and fire UI events
/// immediately. Background/inactive lifecycle must defer until [resumed] so
/// [_handlePendingUpdate] runs when a BuildContext exists for i18n refresh.
@visibleForTesting
bool shouldDeliverArtifactRefreshImmediately() =>
cdnShouldDeliverArtifactRefreshImmediately(
WidgetsBinding.instance.lifecycleState,
);

/// Check for updates and update cache if available
/// Sets _hasPendingUpdate flag if updates were fetched
Future<void> _refreshIfStale() async {
Expand All @@ -513,15 +522,16 @@ class CdnDefinitionProvider extends DefinitionProvider {
// Save to persistent cache
await _saveCachedState(jsonString);

// If artifact refresh is enabled and app is already initialized,
// immediately update appBundle and fire refresh event.
// This handles the cold start scenario where background refresh
// completes after initial render.
if (isArtifactRefreshEnabled() && Ensemble().getConfig() != null) {
await _handlePendingUpdate();
} else {
// Mark for later refresh on next resume
_hasPendingUpdate = true;
// Foreground/cold-start: sync appBundle + translations before refresh event.
// Background: defer until resume (see onAppLifecycleStateChanged) so i18n
// refresh is not skipped when context is unavailable.
if (isArtifactRefreshEnabled()) {
if (shouldDeliverArtifactRefreshImmediately() &&
Ensemble().getConfig() != null) {
await _handlePendingUpdate();
} else {
_hasPendingUpdate = true;
}
}
} catch (e) {
if (kDebugMode) {
Expand Down Expand Up @@ -947,3 +957,9 @@ class CdnDefinitionProvider extends DefinitionProvider {
}
}
}

/// True when CDN artifact refresh events may be delivered immediately.
@visibleForTesting
bool cdnShouldDeliverArtifactRefreshImmediately(AppLifecycleState? state) {
return state == null || state == AppLifecycleState.resumed;
}
29 changes: 29 additions & 0 deletions modules/ensemble/test/cdn_provider_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,35 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
group('cdnShouldDeliverArtifactRefreshImmediately', () {
test('allows delivery when lifecycle is unknown or resumed', () {
expect(cdnShouldDeliverArtifactRefreshImmediately(null), isTrue);
expect(
cdnShouldDeliverArtifactRefreshImmediately(AppLifecycleState.resumed),
isTrue,
);
});

test('defers delivery while app is backgrounded or inactive', () {
expect(
cdnShouldDeliverArtifactRefreshImmediately(AppLifecycleState.paused),
isFalse,
);
expect(
cdnShouldDeliverArtifactRefreshImmediately(AppLifecycleState.inactive),
isFalse,
);
expect(
cdnShouldDeliverArtifactRefreshImmediately(AppLifecycleState.hidden),
isFalse,
);
expect(
cdnShouldDeliverArtifactRefreshImmediately(AppLifecycleState.detached),
isFalse,
);
});
});

group('CDN cache invalidation', () {
test('resets freshness metadata when persisted manifest is invalid',
() async {
Expand Down
Loading