Skip to content

Commit 452b8bb

Browse files
committed
chore: support native default value and listen for update
1 parent 4d30a77 commit 452b8bb

File tree

4 files changed

+74
-3
lines changed

4 files changed

+74
-3
lines changed

lib/repositories/feature_flag_providers.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ final featureFlagsProvider =
1010
class FeatureFlagsNotifier extends AsyncNotifier<List<FeatureFlag>> {
1111
@override
1212
Future<List<FeatureFlag>> build() async {
13-
return ref.watch(featureFlagRepositoryProvider).getAllFlags();
13+
final repo = ref.watch(featureFlagRepositoryProvider);
14+
// Listen for real-time updates from Remote Config/Repository
15+
final subscription = repo.onUpdated.listen((_) => ref.invalidateSelf());
16+
ref.onDispose(subscription.cancel);
17+
18+
return repo.getAllFlags();
1419
}
1520

1621
Future<void> refreshFlags() async {

lib/repositories/feature_flag_repository.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ final featureFlagRepositoryProvider = Provider<FeatureFlagRepository>((ref) {
6161
class FeatureFlagRepository {
6262
final FeatureFlagService _service;
6363
final SharedPreferencesAsync _prefs;
64+
final _updateController = StreamController<void>.broadcast();
6465
Map<String, FeatureFlagData>? _defaultsCache;
6566
List<FeatureFlag>? _flagsCache;
6667

@@ -70,6 +71,9 @@ class FeatureFlagRepository {
7071
}) : _service = service,
7172
_prefs = prefs;
7273

74+
/// A stream that emits whenever feature flags are updated from a remote source.
75+
Stream<void> get onUpdated => _updateController.stream;
76+
7377
Future<Map<String, FeatureFlagData>> _getDefaults({
7478
bool forceRefresh = false,
7579
}) async {
@@ -79,6 +83,28 @@ class FeatureFlagRepository {
7983

8084
/// Initializes the repository by warming up the cache.
8185
Future<void> init() async {
86+
// Initialize Remote Config with local defaults
87+
try {
88+
final localJson = await _service.loadLocalJson();
89+
await firebaseService.init(defaults: {'feature_flags': localJson});
90+
91+
// Listen for remote updates
92+
firebaseService.onConfigUpdated.listen((_) {
93+
log(
94+
'Remote Config updated, invalidating feature flag cache',
95+
name: 'FeatureFlagRepository',
96+
);
97+
_defaultsCache = null;
98+
_flagsCache = null;
99+
_updateController.add(null);
100+
});
101+
} catch (e) {
102+
log(
103+
'Failed to initialize Remote Config defaults: $e',
104+
name: 'FeatureFlagRepository',
105+
);
106+
}
107+
82108
await getAllFlags();
83109
log('FeatureFlagRepository initialized', name: 'FeatureFlagRepository');
84110
}

lib/services/feature_flag/feature_flag_service.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ final featureFlagServiceProvider = Provider<FeatureFlagService>((ref) {
1313
/// Service for fetching default feature flags from the local JSON config.
1414
/// Extended with Firebase Remote Config to allow remote overrides.
1515
class FeatureFlagService {
16+
Future<String> loadLocalJson() async {
17+
return await rootBundle.loadString('assets/feature_flags.json');
18+
}
19+
1620
Future<Map<String, FeatureFlagData>> fetchDefaultFlags() async {
17-
final jsonString = await rootBundle.loadString('assets/feature_flags.json');
21+
final jsonString = await loadLocalJson();
1822
final localDefaults = jsonDecode(jsonString) as Map<String, dynamic>;
1923

2024
final result = firebaseService.getRemoteConfigString('feature_flags');

lib/services/firebase_service.dart

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:firebase_analytics/firebase_analytics.dart';
22
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
33
import 'package:firebase_remote_config/firebase_remote_config.dart';
4+
import 'dart:async';
45
import 'dart:developer' as dev;
56

67
/// Global toggle for Firebase features.
@@ -25,8 +26,14 @@ var firebaseService = const FirebaseService();
2526
/// firebase.crashlytics?.recordError(e, stack);
2627
/// ```
2728
class FirebaseService {
29+
static final _updateController = StreamController<void>.broadcast();
30+
static bool _isInitialized = false;
31+
2832
const FirebaseService();
2933

34+
/// A stream that emits whenever the Remote Config is updated and activated.
35+
Stream<void> get onConfigUpdated => _updateController.stream;
36+
3037
/// The [FirebaseAnalytics] instance, or `null` if Firebase is disabled.
3138
FirebaseAnalytics? get analytics =>
3239
useFirebase ? FirebaseAnalytics.instance : null;
@@ -64,15 +71,44 @@ class FirebaseService {
6471
/// Initializes Firebase Remote Config.
6572
///
6673
/// Call this at app start to fetch and activate the latest configuration.
67-
Future<void> init() async {
74+
/// Optionally provide [defaults] for in-app default values.
75+
Future<void> init({Map<String, dynamic>? defaults}) async {
6876
final rc = remoteConfig;
6977
if (rc == null) return;
7078

79+
if (_isInitialized) {
80+
if (defaults != null) await setDefaults(defaults);
81+
return;
82+
}
83+
84+
if (defaults != null) {
85+
await rc.setDefaults(defaults);
86+
}
87+
88+
_setupUpdateListener(rc);
89+
7190
await _fetchAndActivate(
7291
rc,
7392
context: 'initialized',
7493
setupSettings: true,
7594
);
95+
_isInitialized = true;
96+
}
97+
98+
/// Updates the in-app default values for Remote Config.
99+
Future<void> setDefaults(Map<String, dynamic> defaults) async {
100+
await remoteConfig?.setDefaults(defaults);
101+
}
102+
103+
void _setupUpdateListener(FirebaseRemoteConfig rc) {
104+
rc.onConfigUpdated.listen((event) async {
105+
dev.log(
106+
'Remote Config updated: ${event.updatedKeys}',
107+
name: 'FirebaseService',
108+
);
109+
await rc.activate();
110+
_updateController.add(null);
111+
});
76112
}
77113

78114
/// Forces a fresh fetch and activation of Remote Config.

0 commit comments

Comments
 (0)