Skip to content
Open
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
9 changes: 9 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,15 @@ jobs:
- name: Run WIF auth test
run: dart test test/integration/app/firebase_app_prod_test.dart --concurrency=1 --tags wif

- name: Run Remote Config integration tests
env:
SERVICE_ACCOUNT: ${{ secrets.SERVICE_ACCOUNT }}
run: |
project_id="${SERVICE_ACCOUNT##*@}"
project_id="${project_id%%.iam.gserviceaccount.com}"
RC_TEST_PROJECT_ID="$project_id" \
dart test test/integration/remote_config/ --concurrency=1

publish:
name: Publish verification
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions packages/firebase_admin_sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## 0.5.2-wip

- Add Remote Config support: template management and server-side template evaluation.
- Remove dependency on `package:equatable`.
- Make `Query`, `CollectionReference`, `DocumentReference`, and `CollectionGroup` mockable.
- `Credential.createClient(List<String> scopes)` — create an authenticated `AuthClient` directly
Expand Down
81 changes: 80 additions & 1 deletion packages/firebase_admin_sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- [App Check](#app-check)
- [Firestore](#firestore)
- [Functions](#functions)
- [Remote Config](#remote-config)
- [Messaging](#messaging)
- [Storage](#storage)
- [Security Rules](#security-rules)
Expand Down Expand Up @@ -57,7 +58,7 @@ The Firebase Admin Dart SDK currently supports the following Firebase services:
| Machine Learning | 🔴 | |
| Messaging | 🟢 | |
| Project Management | 🔴 | |
| Remote Config | 🔴 | |
| Remote Config | 🟢 | |
| Security Rules | 🟢 | |
| Storage | 🟢 | Via [package:google_cloud_storage] |

Expand Down Expand Up @@ -583,6 +584,84 @@ await queue.enqueue(
await queue.delete('payment-order-456');
```

### Remote Config

```dart
import 'package:firebase_admin_sdk/firebase_admin_sdk.dart';
import 'package:firebase_admin_sdk/remote_config.dart';

final app = FirebaseApp.initializeApp();
final remoteConfig = app.remoteConfig();
```

#### getTemplate / publishTemplate

```dart
// Read the active template, edit one parameter, and publish.
final template = await remoteConfig.getTemplate();
final updated = RemoteConfigTemplate(
etag: template.etag,
conditions: template.conditions,
parameters: {
...template.parameters,
'welcome_message': RemoteConfigParameter(
defaultValue: const ExplicitParameterValue(value: 'Hello'),
description: 'Updated greeting',
),
},
parameterGroups: template.parameterGroups,
version: template.version,
);
final published = await remoteConfig.publishTemplate(updated);
print('Published version: ${published.version?.versionNumber}');
```

#### validateTemplate

```dart
// Server-side validation without publishing.
final validated = await remoteConfig.validateTemplate(template);
print('Template OK; etag: ${validated.etag}');
```

#### listVersions / rollback

```dart
final versions = await remoteConfig.listVersions(
ListVersionsOptions(pageSize: 10),
);
for (final v in versions.versions) {
print('v${v.versionNumber}: ${v.description}');
}

// Roll back to a previous version.
await remoteConfig.rollback('5');
```

#### Server-side evaluation

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.

```dart
final template = await remoteConfig.getServerTemplate(
defaultConfig: {'enable_new_ui': false, 'max_items': 50},
);

// Evaluate against an EvaluationContext: randomizationId is used by percent
// conditions, custom signals are used by string/numeric/semver conditions.
final config = template.evaluate(
EvaluationContext(
randomizationId: '<user-id>',
customSignals: {'app_version': '2.3.1', 'country': 'US'},
),
);

if (config.getBoolean('enable_new_ui')) {
// ...
}
print('max items: ${config.getInt('max_items')}');
```

### Messaging

```dart
Expand Down
1 change: 1 addition & 0 deletions packages/firebase_admin_sdk/example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@ Some examples require a real project and credentials and are commented out by de
- **App Check**
- **Messaging**
- **Security Rules**
- **Remote Config**

You can uncomment them in `bin/example.dart` to try them out if you have a properly configured project.
4 changes: 4 additions & 0 deletions packages/firebase_admin_sdk/example/bin/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import 'package:firebase_admin_sdk_example/auth_example.dart';
import 'package:firebase_admin_sdk_example/firestore_example.dart';
import 'package:firebase_admin_sdk_example/functions_example.dart';
import 'package:firebase_admin_sdk_example/messaging_example.dart';
import 'package:firebase_admin_sdk_example/remote_config_example.dart';
import 'package:firebase_admin_sdk_example/security_rules_example.dart';
import 'package:firebase_admin_sdk_example/storage_example.dart';

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

// Uncomment to run security rules example (requires a real project and credentials)
// await securityRulesExample(admin);

// Uncomment to run remote config example (requires a real project and credentials)
// await remoteConfigExample(admin);
} finally {
await admin.close();
}
Expand Down
118 changes: 118 additions & 0 deletions packages/firebase_admin_sdk/example/lib/remote_config_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import 'package:firebase_admin_sdk/firebase_admin_sdk.dart';
import 'package:firebase_admin_sdk/remote_config.dart';

Future<void> remoteConfigExample(FirebaseApp admin) async {
print('\n### Remote Config Example ###\n');

final remoteConfig = admin.remoteConfig();

// Example 1: Read the active template.
RemoteConfigTemplate? template;
try {
print('> Fetching active template...\n');
template = await remoteConfig.getTemplate();
print('Template fetched!');
print(' - ETag: ${template.etag}');
print(' - Conditions: ${template.conditions.length}');
print(' - Parameters: ${template.parameters.length}');
print(' - Parameter groups: ${template.parameterGroups.length}');
if (template.version != null) {
print(' - Version: ${template.version!.versionNumber}');
}
print('');
} on FirebaseRemoteConfigException catch (e) {
print('> Remote Config error: ${e.code} - ${e.message}');
return;
} catch (e) {
print('> Error fetching template: $e');
return;
}

// Example 2: Validate a modified template without publishing.
try {
print('> Validating a modified template (no publish)...\n');
final modified = RemoteConfigTemplate(
etag: template.etag,
conditions: template.conditions,
parameters: <String, RemoteConfigParameter>{
...template.parameters,
'dart_admin_sdk_demo': RemoteConfigParameter(
defaultValue: const ExplicitParameterValue(value: 'hello'),
description: 'Demo parameter from the Dart Admin SDK example.',
valueType: ParameterValueType.string,
),
},
parameterGroups: template.parameterGroups,
version: template.version,
);
final validated = await remoteConfig.validateTemplate(modified);
print('Template validated!');
print(' - ETag (restored): ${validated.etag}');
print('');
} on FirebaseRemoteConfigException catch (e) {
print('> Remote Config error: ${e.code} - ${e.message}');
} catch (e) {
print('> Error validating template: $e');
}

// Example 3: List published versions.
try {
print('> Listing published versions (page size 5)...\n');
final result = await remoteConfig.listVersions(
ListVersionsOptions(pageSize: 5),
);
print('Got ${result.versions.length} version(s):');
for (final v in result.versions) {
print(' - v${v.versionNumber}: ${v.description ?? '(no description)'}');
}
print('');
} on FirebaseRemoteConfigException catch (e) {
print('> Remote Config error: ${e.code} - ${e.message}');
} catch (e) {
print('> Error listing versions: $e');
}

// Example 4: Server-side template evaluation.
try {
print('> Fetching server template and evaluating...\n');
final serverTemplate = await remoteConfig.getServerTemplate(
defaultConfig: const <String, Object>{
'enable_new_ui': false,
'max_items': 50,
},
);
final config = serverTemplate.evaluate(
const EvaluationContext(
randomizationId: 'demo-user-id',
customSignals: <String, Object>{
'app_version': '2.3.1',
'country': 'US',
},
),
);
print('Server config evaluated!');
print(' - enable_new_ui: ${config.getBoolean('enable_new_ui')}');
print(' - max_items: ${config.getInt('max_items')}');
final all = config.getAll();
print(' - total resolved keys: ${all.length}');
print('');
} on FirebaseRemoteConfigException catch (e) {
print('> Remote Config error: ${e.code} - ${e.message}');
} catch (e) {
print('> Error evaluating server template: $e');
}
}
21 changes: 21 additions & 0 deletions packages/firebase_admin_sdk/lib/remote_config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

export 'src/remote_config/remote_config.dart'
hide
ConditionEvaluator,
RemoteConfigHttpClient,
RemoteConfigHttpResult,
RemoteConfigRequestHandler,
remoteConfigErrorCodeMapping;
1 change: 1 addition & 0 deletions packages/firebase_admin_sdk/lib/src/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import '../auth.dart';
import '../firestore.dart';
import '../functions.dart';
import '../messaging.dart';
import '../remote_config.dart';
import '../security_rules.dart';
import '../storage.dart';
import 'utils/utils.dart';
Expand Down
5 changes: 5 additions & 0 deletions packages/firebase_admin_sdk/lib/src/app/firebase_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ class FirebaseApp {
/// Returns a cached instance if one exists, otherwise creates a new one.
Messaging messaging() => Messaging.internal(this);

/// Gets the Remote Config service instance for this app.
///
/// Returns a cached instance if one exists, otherwise creates a new one.
RemoteConfig remoteConfig() => RemoteConfig.internal(this);

/// Gets the Security Rules service instance for this app.
///
/// Returns a cached instance if one exists, otherwise creates a new one.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum FirebaseServiceType {
auth(name: 'auth'),
firestore(name: 'firestore'),
messaging(name: 'messaging'),
remoteConfig(name: 'remote-config'),
securityRules(name: 'security-rules'),
functions(name: 'functions'),
storage(name: 'storage');
Expand Down
Loading
Loading