Skip to content

Commit ab9af3d

Browse files
authored
feat(remote_config): add Firebase Remote Config support
* feat: add Firebase Remote Config support Implements Firebase Remote Config for the Dart Admin SDK with full template management plus server-side template evaluation.
1 parent ed382bf commit ab9af3d

22 files changed

Lines changed: 4527 additions & 1 deletion

.github/workflows/build.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,15 @@ jobs:
287287
- name: Run WIF auth test
288288
run: dart test test/integration/app/firebase_app_prod_test.dart --concurrency=1
289289

290+
- name: Run Remote Config integration tests
291+
env:
292+
SERVICE_ACCOUNT: ${{ secrets.SERVICE_ACCOUNT }}
293+
run: |
294+
project_id="${SERVICE_ACCOUNT##*@}"
295+
project_id="${project_id%%.iam.gserviceaccount.com}"
296+
RC_TEST_PROJECT_ID="$project_id" \
297+
dart test test/integration/remote_config/ --concurrency=1
298+
290299
publish:
291300
name: Publish verification
292301
runs-on: ubuntu-latest

packages/firebase_admin_sdk/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## 0.5.2-wip
22

3+
- Add Remote Config support: template management and server-side template evaluation.
34
- Remove dependency on `package:equatable`.
45
- Make `Query`, `CollectionReference`, `DocumentReference`, and `CollectionGroup` mockable.
56

packages/firebase_admin_sdk/README.md

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- [App Check](#app-check)
1919
- [Firestore](#firestore)
2020
- [Functions](#functions)
21+
- [Remote Config](#remote-config)
2122
- [Messaging](#messaging)
2223
- [Storage](#storage)
2324
- [Security Rules](#security-rules)
@@ -57,7 +58,7 @@ The Firebase Admin Dart SDK currently supports the following Firebase services:
5758
| Machine Learning | 🔴 | |
5859
| Messaging | 🟢 | |
5960
| Project Management | 🔴 | |
60-
| Remote Config | 🔴 | |
61+
| Remote Config | 🟢 | |
6162
| Security Rules | 🟢 | |
6263
| Storage | 🟢 | Via [package:google_cloud_storage] |
6364

@@ -578,6 +579,84 @@ await queue.enqueue(
578579
await queue.delete('payment-order-456');
579580
```
580581

582+
### Remote Config
583+
584+
```dart
585+
import 'package:firebase_admin_sdk/firebase_admin_sdk.dart';
586+
import 'package:firebase_admin_sdk/remote_config.dart';
587+
588+
final app = FirebaseApp.initializeApp();
589+
final remoteConfig = app.remoteConfig();
590+
```
591+
592+
#### getTemplate / publishTemplate
593+
594+
```dart
595+
// Read the active template, edit one parameter, and publish.
596+
final template = await remoteConfig.getTemplate();
597+
final updated = RemoteConfigTemplate(
598+
etag: template.etag,
599+
conditions: template.conditions,
600+
parameters: {
601+
...template.parameters,
602+
'welcome_message': RemoteConfigParameter(
603+
defaultValue: const ExplicitParameterValue(value: 'Hello'),
604+
description: 'Updated greeting',
605+
),
606+
},
607+
parameterGroups: template.parameterGroups,
608+
version: template.version,
609+
);
610+
final published = await remoteConfig.publishTemplate(updated);
611+
print('Published version: ${published.version?.versionNumber}');
612+
```
613+
614+
#### validateTemplate
615+
616+
```dart
617+
// Server-side validation without publishing.
618+
final validated = await remoteConfig.validateTemplate(template);
619+
print('Template OK; etag: ${validated.etag}');
620+
```
621+
622+
#### listVersions / rollback
623+
624+
```dart
625+
final versions = await remoteConfig.listVersions(
626+
ListVersionsOptions(pageSize: 10),
627+
);
628+
for (final v in versions.versions) {
629+
print('v${v.versionNumber}: ${v.description}');
630+
}
631+
632+
// Roll back to a previous version.
633+
await remoteConfig.rollback('5');
634+
```
635+
636+
#### Server-side evaluation
637+
638+
Use [`getServerTemplate`](https://firebase.google.com/docs/remote-config/server-side-config) to fetch a template that the SDK can evaluate locally — useful for runtime feature flags, A/B test bucketing, and server-rendered configuration.
639+
640+
```dart
641+
final template = await remoteConfig.getServerTemplate(
642+
defaultConfig: {'enable_new_ui': false, 'max_items': 50},
643+
);
644+
645+
// Evaluate against an EvaluationContext: randomizationId is used by percent
646+
// conditions, custom signals are used by string/numeric/semver conditions.
647+
final config = template.evaluate(
648+
EvaluationContext(
649+
randomizationId: '<user-id>',
650+
customSignals: {'app_version': '2.3.1', 'country': 'US'},
651+
),
652+
);
653+
654+
if (config.getBoolean('enable_new_ui')) {
655+
// ...
656+
}
657+
print('max items: ${config.getInt('max_items')}');
658+
```
659+
581660
### Messaging
582661

583662
```dart

packages/firebase_admin_sdk/example/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@ Some examples require a real project and credentials and are commented out by de
4646
- **App Check**
4747
- **Messaging**
4848
- **Security Rules**
49+
- **Remote Config**
4950

5051
You can uncomment them in `bin/example.dart` to try them out if you have a properly configured project.

packages/firebase_admin_sdk/example/bin/example.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import 'package:firebase_admin_sdk_example/auth_example.dart';
2121
import 'package:firebase_admin_sdk_example/firestore_example.dart';
2222
import 'package:firebase_admin_sdk_example/functions_example.dart';
2323
import 'package:firebase_admin_sdk_example/messaging_example.dart';
24+
import 'package:firebase_admin_sdk_example/remote_config_example.dart';
2425
import 'package:firebase_admin_sdk_example/security_rules_example.dart';
2526
import 'package:firebase_admin_sdk_example/storage_example.dart';
2627

@@ -55,6 +56,9 @@ Future<void> main() async {
5556

5657
// Uncomment to run security rules example (requires a real project and credentials)
5758
// await securityRulesExample(admin);
59+
60+
// Uncomment to run remote config example (requires a real project and credentials)
61+
// await remoteConfigExample(admin);
5862
} finally {
5963
await admin.close();
6064
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import 'package:firebase_admin_sdk/firebase_admin_sdk.dart';
16+
import 'package:firebase_admin_sdk/remote_config.dart';
17+
18+
Future<void> remoteConfigExample(FirebaseApp admin) async {
19+
print('\n### Remote Config Example ###\n');
20+
21+
final remoteConfig = admin.remoteConfig();
22+
23+
// Example 1: Read the active template.
24+
RemoteConfigTemplate? template;
25+
try {
26+
print('> Fetching active template...\n');
27+
template = await remoteConfig.getTemplate();
28+
print('Template fetched!');
29+
print(' - ETag: ${template.etag}');
30+
print(' - Conditions: ${template.conditions.length}');
31+
print(' - Parameters: ${template.parameters.length}');
32+
print(' - Parameter groups: ${template.parameterGroups.length}');
33+
if (template.version != null) {
34+
print(' - Version: ${template.version!.versionNumber}');
35+
}
36+
print('');
37+
} on FirebaseRemoteConfigException catch (e) {
38+
print('> Remote Config error: ${e.code} - ${e.message}');
39+
return;
40+
} catch (e) {
41+
print('> Error fetching template: $e');
42+
return;
43+
}
44+
45+
// Example 2: Validate a modified template without publishing.
46+
try {
47+
print('> Validating a modified template (no publish)...\n');
48+
final modified = RemoteConfigTemplate(
49+
etag: template.etag,
50+
conditions: template.conditions,
51+
parameters: <String, RemoteConfigParameter>{
52+
...template.parameters,
53+
'dart_admin_sdk_demo': RemoteConfigParameter(
54+
defaultValue: const ExplicitParameterValue(value: 'hello'),
55+
description: 'Demo parameter from the Dart Admin SDK example.',
56+
valueType: ParameterValueType.string,
57+
),
58+
},
59+
parameterGroups: template.parameterGroups,
60+
version: template.version,
61+
);
62+
final validated = await remoteConfig.validateTemplate(modified);
63+
print('Template validated!');
64+
print(' - ETag (restored): ${validated.etag}');
65+
print('');
66+
} on FirebaseRemoteConfigException catch (e) {
67+
print('> Remote Config error: ${e.code} - ${e.message}');
68+
} catch (e) {
69+
print('> Error validating template: $e');
70+
}
71+
72+
// Example 3: List published versions.
73+
try {
74+
print('> Listing published versions (page size 5)...\n');
75+
final result = await remoteConfig.listVersions(
76+
ListVersionsOptions(pageSize: 5),
77+
);
78+
print('Got ${result.versions.length} version(s):');
79+
for (final v in result.versions) {
80+
print(' - v${v.versionNumber}: ${v.description ?? '(no description)'}');
81+
}
82+
print('');
83+
} on FirebaseRemoteConfigException catch (e) {
84+
print('> Remote Config error: ${e.code} - ${e.message}');
85+
} catch (e) {
86+
print('> Error listing versions: $e');
87+
}
88+
89+
// Example 4: Server-side template evaluation.
90+
try {
91+
print('> Fetching server template and evaluating...\n');
92+
final serverTemplate = await remoteConfig.getServerTemplate(
93+
defaultConfig: const <String, Object>{
94+
'enable_new_ui': false,
95+
'max_items': 50,
96+
},
97+
);
98+
final config = serverTemplate.evaluate(
99+
const EvaluationContext(
100+
randomizationId: 'demo-user-id',
101+
customSignals: <String, Object>{
102+
'app_version': '2.3.1',
103+
'country': 'US',
104+
},
105+
),
106+
);
107+
print('Server config evaluated!');
108+
print(' - enable_new_ui: ${config.getBoolean('enable_new_ui')}');
109+
print(' - max_items: ${config.getInt('max_items')}');
110+
final all = config.getAll();
111+
print(' - total resolved keys: ${all.length}');
112+
print('');
113+
} on FirebaseRemoteConfigException catch (e) {
114+
print('> Remote Config error: ${e.code} - ${e.message}');
115+
} catch (e) {
116+
print('> Error evaluating server template: $e');
117+
}
118+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
export 'src/remote_config/remote_config.dart'
16+
hide
17+
ConditionEvaluator,
18+
RemoteConfigHttpClient,
19+
RemoteConfigHttpResult,
20+
RemoteConfigRequestHandler,
21+
remoteConfigErrorCodeMapping;

packages/firebase_admin_sdk/lib/src/app.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import '../auth.dart';
3232
import '../firestore.dart';
3333
import '../functions.dart';
3434
import '../messaging.dart';
35+
import '../remote_config.dart';
3536
import '../security_rules.dart';
3637
import '../storage.dart';
3738
import 'utils/utils.dart';

packages/firebase_admin_sdk/lib/src/app/firebase_app.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ class FirebaseApp {
234234
/// Returns a cached instance if one exists, otherwise creates a new one.
235235
Messaging messaging() => Messaging.internal(this);
236236

237+
/// Gets the Remote Config service instance for this app.
238+
///
239+
/// Returns a cached instance if one exists, otherwise creates a new one.
240+
RemoteConfig remoteConfig() => RemoteConfig.internal(this);
241+
237242
/// Gets the Security Rules service instance for this app.
238243
///
239244
/// Returns a cached instance if one exists, otherwise creates a new one.

packages/firebase_admin_sdk/lib/src/app/firebase_service.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ enum FirebaseServiceType {
2020
auth(name: 'auth'),
2121
firestore(name: 'firestore'),
2222
messaging(name: 'messaging'),
23+
remoteConfig(name: 'remote-config'),
2324
securityRules(name: 'security-rules'),
2425
functions(name: 'functions'),
2526
storage(name: 'storage');

0 commit comments

Comments
 (0)