diff --git a/packages/firebase_ai/firebase_ai/android/build.gradle b/packages/firebase_ai/firebase_ai/android/build.gradle
index 278104b4b9c2..2905ef640cae 100644
--- a/packages/firebase_ai/firebase_ai/android/build.gradle
+++ b/packages/firebase_ai/firebase_ai/android/build.gradle
@@ -21,6 +21,24 @@ buildscript {
}
}
+repositories {
+ google()
+ mavenCentral()
+}
+
+def firebaseCoreProject = findProject(':firebase_core')
+if (firebaseCoreProject == null) {
+ throw new GradleException('Could not find the firebase_core FlutterFire plugin, have you added it as a dependency in your pubspec?')
+} else if (!firebaseCoreProject.properties['FirebaseSDKVersion']) {
+ throw new GradleException('A newer version of the firebase_core FlutterFire plugin is required, please update your firebase_core pubspec dependency.')
+}
+
+def getRootProjectExtOrCoreProperty(name, firebaseCoreProject) {
+ if (!rootProject.ext.has('FlutterFire')) return firebaseCoreProject.properties[name]
+ if (!rootProject.ext.get('FlutterFire')[name]) return firebaseCoreProject.properties[name]
+ return rootProject.ext.get('FlutterFire').get(name)
+}
+
android {
if (project.android.hasProperty("namespace")) {
namespace 'io.flutter.plugins.firebase.ai'
@@ -41,9 +59,20 @@ android {
main.java.srcDirs += "src/main/kotlin"
}
+ buildFeatures {
+ buildConfig = true
+ }
+
lintOptions {
disable 'InvalidPackage'
}
+
+ dependencies {
+ api firebaseCoreProject
+ implementation platform("com.google.firebase:firebase-bom:${getRootProjectExtOrCoreProperty("FirebaseSDKVersion", firebaseCoreProject)}")
+ implementation 'com.google.firebase:firebase-common'
+ implementation 'androidx.annotation:annotation:1.7.0'
+ }
}
plugins.withId("org.jetbrains.kotlin.android") {
@@ -53,3 +82,5 @@ plugins.withId("org.jetbrains.kotlin.android") {
}
}
}
+
+apply from: file("./user-agent.gradle")
diff --git a/packages/firebase_ai/firebase_ai/android/src/main/AndroidManifest.xml b/packages/firebase_ai/firebase_ai/android/src/main/AndroidManifest.xml
index db54cec9bea5..12fbf47e96eb 100644
--- a/packages/firebase_ai/firebase_ai/android/src/main/AndroidManifest.xml
+++ b/packages/firebase_ai/firebase_ai/android/src/main/AndroidManifest.xml
@@ -1,3 +1,9 @@
+
+
+
+
+
diff --git a/packages/firebase_ai/firebase_ai/android/src/main/kotlin/io/flutter/plugins/firebase/ai/FlutterFirebaseAppRegistrar.kt b/packages/firebase_ai/firebase_ai/android/src/main/kotlin/io/flutter/plugins/firebase/ai/FlutterFirebaseAppRegistrar.kt
new file mode 100644
index 000000000000..6c69cea53ef1
--- /dev/null
+++ b/packages/firebase_ai/firebase_ai/android/src/main/kotlin/io/flutter/plugins/firebase/ai/FlutterFirebaseAppRegistrar.kt
@@ -0,0 +1,28 @@
+// 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.
+
+package io.flutter.plugins.firebase.ai
+
+import androidx.annotation.Keep
+import com.google.firebase.components.Component
+import com.google.firebase.components.ComponentRegistrar
+import com.google.firebase.platforminfo.LibraryVersionComponent
+
+@Keep
+class FlutterFirebaseAppRegistrar : ComponentRegistrar {
+ override fun getComponents(): List> {
+ return listOf(
+ LibraryVersionComponent.create(BuildConfig.LIBRARY_NAME, BuildConfig.LIBRARY_VERSION))
+ }
+}
diff --git a/packages/firebase_ai/firebase_ai/android/user-agent.gradle b/packages/firebase_ai/firebase_ai/android/user-agent.gradle
new file mode 100644
index 000000000000..863c976dcaef
--- /dev/null
+++ b/packages/firebase_ai/firebase_ai/android/user-agent.gradle
@@ -0,0 +1,22 @@
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+String libraryVersionName = "UNKNOWN"
+String libraryName = "flutter-fire-ai"
+File pubspec = new File(project.projectDir.parentFile, 'pubspec.yaml')
+
+if (pubspec.exists()) {
+ String yaml = pubspec.text
+ // Using \s*['|"]?([^\n|'|"]*)['|"]? to extract version number.
+ Matcher versionMatcher = Pattern.compile("^version:\\s*['|\"]?([^\\n|'|\"]*)['|\"]?\$", Pattern.MULTILINE).matcher(yaml)
+ if (versionMatcher.find()) libraryVersionName = versionMatcher.group(1).replaceAll("\\+", "-")
+}
+
+android {
+ defaultConfig {
+ // BuildConfig.VERSION_NAME
+ buildConfigField 'String', 'LIBRARY_VERSION', "\"${libraryVersionName}\""
+ // BuildConfig.LIBRARY_NAME
+ buildConfigField 'String', 'LIBRARY_NAME', "\"${libraryName}\""
+ }
+}
diff --git a/packages/firebase_ai/firebase_ai/ios/firebase_ai/Sources/firebase_ai/Constants.swift b/packages/firebase_ai/firebase_ai/ios/firebase_ai/Sources/firebase_ai/Constants.swift
new file mode 100644
index 000000000000..f48f80600feb
--- /dev/null
+++ b/packages/firebase_ai/firebase_ai/ios/firebase_ai/Sources/firebase_ai/Constants.swift
@@ -0,0 +1,16 @@
+// 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.
+
+/// Auto-generated file. Do not edit.
+public let versionNumber = "5.0.0"
diff --git a/packages/firebase_ai/firebase_ai/ios/firebase_ai/Sources/firebase_ai/FirebaseAIPlugin.swift b/packages/firebase_ai/firebase_ai/ios/firebase_ai/Sources/firebase_ai/FirebaseAIPlugin.swift
index a76212182bda..e0ae35823168 100644
--- a/packages/firebase_ai/firebase_ai/ios/firebase_ai/Sources/firebase_ai/FirebaseAIPlugin.swift
+++ b/packages/firebase_ai/firebase_ai/ios/firebase_ai/Sources/firebase_ai/FirebaseAIPlugin.swift
@@ -32,6 +32,17 @@ public class FirebaseAIPlugin: NSObject, FlutterPlugin {
)
let instance = FirebaseAIPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
+
+ let selector = NSSelectorFromString("registerLibrary:withVersion:")
+ if let firAppClass = NSClassFromString("FIRApp") as? NSObject.Type,
+ firAppClass.responds(to: selector)
+ {
+ _ = firAppClass.perform(
+ selector,
+ with: "flutter-fire-ai",
+ with: versionNumber
+ )
+ }
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
diff --git a/packages/firebase_ai/firebase_ai/lib/src/firebase_ai.dart b/packages/firebase_ai/firebase_ai/lib/src/firebase_ai.dart
index 188e46dc015d..82a37d2e7754 100644
--- a/packages/firebase_ai/firebase_ai/lib/src/firebase_ai.dart
+++ b/packages/firebase_ai/firebase_ai/lib/src/firebase_ai.dart
@@ -22,6 +22,9 @@ import 'package:meta/meta.dart';
import '../firebase_ai.dart';
import 'base_model.dart';
+import 'firebaseai_version.dart';
+import 'platform_version_stub.dart'
+ if (dart.library.js_interop) 'platform_version_web.dart';
const _defaultVertexAILocation = 'us-central1';
const _defaultAgentPlatformLocation = 'global';
@@ -36,7 +39,9 @@ class FirebaseAI extends FirebasePlugin {
this.auth,
this.useLimitedUseAppCheckTokens = false,
}) : _useAgentPlatform = useAgentPlatform,
- super(app.name, 'plugins.flutter.io/firebase_vertexai');
+ super(app.name, 'plugins.flutter.io/firebase_vertexai') {
+ registerWebLibraryVersion('flutter-fire-ai', packageVersion);
+ }
/// The [FirebaseApp] for this current [FirebaseAI] instance.
FirebaseApp app;
diff --git a/packages/firebase_ai/firebase_ai/lib/src/platform_version_stub.dart b/packages/firebase_ai/firebase_ai/lib/src/platform_version_stub.dart
new file mode 100644
index 000000000000..4c4ca73600e9
--- /dev/null
+++ b/packages/firebase_ai/firebase_ai/lib/src/platform_version_stub.dart
@@ -0,0 +1,16 @@
+// 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.
+
+/// Stub implementation of [registerWebLibraryVersion] on non-web platforms.
+void registerWebLibraryVersion(String libraryName, String version) {}
diff --git a/packages/firebase_ai/firebase_ai/lib/src/platform_version_web.dart b/packages/firebase_ai/firebase_ai/lib/src/platform_version_web.dart
new file mode 100644
index 000000000000..bd42f290be0c
--- /dev/null
+++ b/packages/firebase_ai/firebase_ai/lib/src/platform_version_web.dart
@@ -0,0 +1,40 @@
+// 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 'dart:js_interop';
+import 'dart:js_interop_unsafe';
+
+bool _registered = false;
+
+/// Registers [libraryName] and [version] with the Firebase Web JS SDK.
+void registerWebLibraryVersion(String libraryName, String version) {
+ if (_registered) return;
+ try {
+ final core = globalContext.getProperty('firebase_core'.toJS) as JSObject?;
+ if (core != null && core.hasProperty('registerVersion'.toJS).toDart) {
+ final storage =
+ globalContext.getProperty('sessionStorage'.toJS) as JSObject?;
+ final key = 'flutterfire-$libraryName-$version'.toJS;
+ if (storage?.callMethod('getItem'.toJS, key) == null) {
+ storage?.callMethod('setItem'.toJS, key, version.toJS);
+ core.callMethod(
+ 'registerVersion'.toJS,
+ libraryName.toJS,
+ version.toJS,
+ );
+ }
+ _registered = true;
+ }
+ } catch (_) {}
+}
diff --git a/packages/firebase_ai/firebase_ai/macos/firebase_ai/Sources/firebase_ai/Constants.swift b/packages/firebase_ai/firebase_ai/macos/firebase_ai/Sources/firebase_ai/Constants.swift
new file mode 120000
index 000000000000..7fdbf7a4f382
--- /dev/null
+++ b/packages/firebase_ai/firebase_ai/macos/firebase_ai/Sources/firebase_ai/Constants.swift
@@ -0,0 +1 @@
+../../../../ios/firebase_ai/Sources/firebase_ai/Constants.swift
\ No newline at end of file
diff --git a/packages/firebase_core/firebase_core_web/lib/src/firebase_core_web.dart b/packages/firebase_core/firebase_core_web/lib/src/firebase_core_web.dart
index ea15ec3cc17a..d006a0ccd93f 100644
--- a/packages/firebase_core/firebase_core_web/lib/src/firebase_core_web.dart
+++ b/packages/firebase_core/firebase_core_web/lib/src/firebase_core_web.dart
@@ -87,6 +87,9 @@ class FirebaseCoreWeb extends FirebasePlatform {
static void registerLibraryVersion(String libraryName, String version) {
_libraryVersions[libraryName] = version;
+ if (globalContext.getProperty('firebase_core'.toJS) != null) {
+ _registerVersionIfNeeded(libraryName, version);
+ }
}
static void _registerAllLibraryVersions() {
diff --git a/packages/firebase_core/firebase_core_web/test/firebase_core_web_late_registration_test.dart b/packages/firebase_core/firebase_core_web/test/firebase_core_web_late_registration_test.dart
new file mode 100644
index 000000000000..4b0b1fc85b1e
--- /dev/null
+++ b/packages/firebase_core/firebase_core_web/test/firebase_core_web_late_registration_test.dart
@@ -0,0 +1,88 @@
+// Copyright 2026 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+@TestOn('browser')
+library;
+
+import 'dart:js_interop';
+import 'dart:js_interop_unsafe';
+
+import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart';
+import 'package:firebase_core_web/firebase_core_web.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:web/web.dart' as web;
+
+String _getPlatformInfoString() {
+ final core = globalContext.getProperty('firebase_core'.toJS);
+ final jsApp = core.callMethod('getApp'.toJS);
+ final container = jsApp.getProperty('container'.toJS);
+ final provider = container.callMethod(
+ 'getProvider'.toJS,
+ 'platform-logger'.toJS,
+ );
+ final logger = provider.callMethod('getImmediate'.toJS);
+ return logger.callMethod('getPlatformInfoString'.toJS).toDart;
+}
+
+void main() {
+ group('FirebaseCoreWeb late library version registration', () {
+ setUp(() {
+ FirebasePlatform.instance = FirebaseCoreWeb();
+ web.window.sessionStorage.clear();
+ });
+
+ test(
+ 'registers library version with PlatformLogger when called after initializeApp',
+ () async {
+ const options = FirebaseOptions(
+ apiKey: 'fake-api-key',
+ appId: '1:1234567890:web:fake',
+ messagingSenderId: '1234567890',
+ projectId: 'fake-project',
+ authDomain: 'fake-project.firebaseapp.com',
+ );
+
+ await FirebasePlatform.instance.initializeApp(options: options);
+
+ final initialPlatformInfo = _getPlatformInfoString();
+ expect(initialPlatformInfo, contains('flutter-fire-core/'));
+ expect(
+ initialPlatformInfo,
+ isNot(contains('flutter-fire-late-test/1.2.3')),
+ );
+
+ FirebaseCoreWeb.registerLibraryVersion(
+ 'flutter-fire-late-test',
+ '1.2.3',
+ );
+
+ expect(
+ web.window.sessionStorage
+ .getItem('flutterfire-flutter-fire-late-test-1.2.3'),
+ '1.2.3',
+ );
+ expect(
+ _getPlatformInfoString(),
+ contains('flutter-fire-late-test/1.2.3'),
+ );
+
+ // Clearing sessionStorage allows re-registering a subsequent version.
+ web.window.sessionStorage.clear();
+ FirebaseCoreWeb.registerLibraryVersion(
+ 'flutter-fire-late-test-2',
+ '2.0.0',
+ );
+ expect(
+ web.window.sessionStorage
+ .getItem('flutterfire-flutter-fire-late-test-2-2.0.0'),
+ '2.0.0',
+ );
+ expect(
+ _getPlatformInfoString(),
+ contains('flutter-fire-late-test-2/2.0.0'),
+ );
+ },
+ );
+ });
+}
diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart
index 165b6f5d62fb..9690f0ccbf43 100644
--- a/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart
+++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart
@@ -20,11 +20,14 @@ import 'package:firebase_data_connect/src/common/common_library.dart';
import 'package:firebase_data_connect/src/core/ref.dart';
import 'package:flutter/foundation.dart';
+import './network/hot_restart_guard_stub.dart'
+ if (dart.library.js_interop) './network/hot_restart_guard_web.dart';
import './network/rest_library.dart';
import './network/transport_library.dart';
import 'cache/cache_data_types.dart';
import 'cache/cache.dart';
+import 'dataconnect_version.dart';
/// DataConnect class
class FirebaseDataConnect extends FirebasePlugin {
@@ -48,6 +51,7 @@ class FirebaseDataConnect extends FirebasePlugin {
connectorConfig.serviceId,
),
super(app.name, 'plugins.flutter.io/firebase_data_connect') {
+ registerWebLibraryVersion('flutter-fire-dc', packageVersion);
_queryManager = QueryManager(this);
if (sdkType != null) {
_sdkType = sdkType;
diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/hot_restart_guard_stub.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/hot_restart_guard_stub.dart
index 7bb61996e100..4e25f6e5e1ef 100644
--- a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/hot_restart_guard_stub.dart
+++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/hot_restart_guard_stub.dart
@@ -17,3 +17,5 @@ String? claimDataConnectWebSocketTransport(String key) => null;
bool isCurrentDataConnectWebSocketTransport(String key, String? token) => true;
void releaseDataConnectWebSocketTransport(String key, String? token) {}
+
+void registerWebLibraryVersion(String libraryName, String version) {}
diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/hot_restart_guard_web.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/hot_restart_guard_web.dart
index 1fdd9cc37718..5066522f2342 100644
--- a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/hot_restart_guard_web.dart
+++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/hot_restart_guard_web.dart
@@ -48,3 +48,26 @@ void releaseDataConnectWebSocketTransport(String key, String? token) {
globalContext.delete(key.toJS);
}
}
+
+bool _registered = false;
+
+void registerWebLibraryVersion(String libraryName, String version) {
+ if (_registered) return;
+ try {
+ final core = globalContext.getProperty('firebase_core'.toJS) as JSObject?;
+ if (core != null && core.hasProperty('registerVersion'.toJS).toDart) {
+ final storage =
+ globalContext.getProperty('sessionStorage'.toJS) as JSObject?;
+ final key = 'flutterfire-$libraryName-$version'.toJS;
+ if (storage?.callMethod('getItem'.toJS, key) == null) {
+ storage?.callMethod('setItem'.toJS, key, version.toJS);
+ core.callMethod(
+ 'registerVersion'.toJS,
+ libraryName.toJS,
+ version.toJS,
+ );
+ }
+ _registered = true;
+ }
+ } catch (_) {}
+}
diff --git a/scripts/generate_versions_spm.dart b/scripts/generate_versions_spm.dart
index 2b7766dedcdb..66f05d871578 100644
--- a/scripts/generate_versions_spm.dart
+++ b/scripts/generate_versions_spm.dart
@@ -118,6 +118,7 @@ void updateLibraryVersionPureSwiftPlugins() {
'firebase_in_app_messaging',
'firebase_crashlytics',
'firebase_core',
+ 'firebase_ai',
];
for (final package in packages) {