Skip to content

Commit f7d8429

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

File tree

5 files changed

+85
-14
lines changed

5 files changed

+85
-14
lines changed

ios/Podfile.lock

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,10 @@ EXTERNAL SOURCES:
259259

260260
SPEC CHECKSUMS:
261261
Firebase: 065f2bb395062046623036d8e6dc857bc2521d56
262-
firebase_analytics: 756f4dfe60543c4703cee7b8e4a599604b1a86d4
263-
firebase_core: 8e6f58412ca227827c366b92e7cee047a2148c60
264-
firebase_crashlytics: d3a550f0132b59d886eca10448df04afd38b18cf
265-
firebase_remote_config: cd42a29a61e8610230e7f74d5646d6392fd7368d
262+
firebase_analytics: ee2db99f55715656363e5d7cbed4f4ddac6b9c31
263+
firebase_core: 98bcc1bd1a097bcb8b1ed6e091de3039802527c4
264+
firebase_crashlytics: 0e7f4fc37ad7ce137ca890b208f562baf8f85de5
265+
firebase_remote_config: 0d060eef0fdfb288ffc41903ba9a60bb963755ea
266266
FirebaseABTesting: a399ffe546392a39b19a5c2fb28bd8ea178a6f47
267267
FirebaseAnalytics: cd7d01d352f3c237c9a0e31552c257cd0b0c0352
268268
FirebaseCore: 428912f751178b06bef0a1793effeb4a5e09a9b8
@@ -275,24 +275,24 @@ SPEC CHECKSUMS:
275275
FirebaseSessions: a2d06fd980431fda934c7a543901aca05fc4edcc
276276
FirebaseSharedSwift: 9d2fa84a46676302b89dbd5e6e62bce2fe376909
277277
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
278-
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
279-
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
278+
flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e
279+
flutter_secure_storage_darwin: 557817588b80e60213cbecb573c45c76b788018d
280280
GoogleAdsOnDeviceConversion: d68c69dd9581a0f5da02617b6f377e5be483970f
281281
GoogleAppMeasurement: fce7c1c90640d2f9f5c56771f71deacb2ba3f98c
282282
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
283283
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
284-
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
284+
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
285285
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
286286
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
287287
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
288-
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
289-
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
288+
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
289+
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
290290
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
291291
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
292292
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
293293
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
294-
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
295-
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
294+
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
295+
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
296296

297297
PODFILE CHECKSUM: ce2a4dd764e1c7aeed6a7cdc5e61d092b6dc6d32
298298

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)