diff --git a/packages/firebase_core/firebase_core/lib/firebase_core.dart b/packages/firebase_core/firebase_core/lib/firebase_core.dart index 2f4df23c7206..466f74ad2acb 100644 --- a/packages/firebase_core/firebase_core/lib/firebase_core.dart +++ b/packages/firebase_core/firebase_core/lib/firebase_core.dart @@ -4,12 +4,16 @@ // found in the LICENSE file. import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart' - hide MethodChannelFirebaseApp, MethodChannelFirebase; + hide MethodChannelFirebaseApp; import 'package:flutter/foundation.dart'; export 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart' - show FirebaseOptions, defaultFirebaseAppName, FirebaseException; + show + FirebaseApp, + FirebaseException, + FirebaseOptions, + FirebaseService, + defaultFirebaseAppName; part 'src/firebase.dart'; -part 'src/firebase_app.dart'; part 'src/port_mapping.dart'; diff --git a/packages/firebase_core/firebase_core/lib/src/firebase.dart b/packages/firebase_core/firebase_core/lib/src/firebase.dart index 3edce466264a..401669f41be6 100644 --- a/packages/firebase_core/firebase_core/lib/src/firebase.dart +++ b/packages/firebase_core/firebase_core/lib/src/firebase.dart @@ -20,12 +20,26 @@ class Firebase { static FirebasePlatform? delegatePackingProperty; static FirebasePlatform get _delegate { - return delegatePackingProperty ??= FirebasePlatform.instance; + final delegate = delegatePackingProperty; + if (delegate != null) { + return delegate; + } + + // The platform interface cannot depend on the `MethodChannel` + // implementation (it has to stay usable without Flutter), which is why the + // default implementation is installed lazily here instead. Platform + // implementations register themselves, for example `FirebaseCoreWeb` does + // so from `registerWith`, in which case they are kept as is. + if (!FirebasePlatform.isRegistered) { + FirebasePlatform.instance = MethodChannelFirebase(); + } + + return delegatePackingProperty = FirebasePlatform.instance; } /// Returns a list of all [FirebaseApp] instances that have been created. static List get apps { - return _delegate.apps.map(FirebaseApp._).toList(growable: false); + return _delegate.apps.map(FirebaseApp.fromPlatform).toList(growable: false); } /// Initializes a new [FirebaseApp] instance by [name] and [options] and @@ -81,7 +95,7 @@ class Firebase { options: resolvedOptions, ); - return FirebaseApp._(app); + return FirebaseApp.fromPlatform(app); } /// Returns a [FirebaseApp] instance. @@ -91,7 +105,7 @@ class Firebase { static FirebaseApp app([String name = defaultFirebaseAppName]) { FirebaseAppPlatform app = _delegate.app(name); - return FirebaseApp._(app); + return FirebaseApp.fromPlatform(app); } // TODO(rrousselGit): remove ==/hashCode diff --git a/packages/firebase_core/firebase_core_common/CHANGELOG.md b/packages/firebase_core/firebase_core_common/CHANGELOG.md new file mode 100644 index 000000000000..ba35a0ae66fc --- /dev/null +++ b/packages/firebase_core/firebase_core_common/CHANGELOG.md @@ -0,0 +1,12 @@ +## 0.1.0 + +- Initial release. +- Pure Dart types and platform interfaces extracted from + `firebase_core_platform_interface` (`FirebaseApp`, `FirebaseAppPlatform`, + `FirebaseOptions`, `FirebaseException`, `FirebasePlatform`, `FirebasePlugin` + and the shared exception helpers), so that Firebase Core platform + implementations can be consumed outside of Flutter. +- `FirebasePlatform.instance` no longer defaults to the `MethodChannel` + implementation: implementations must register themselves (`FirebaseCoreWeb` + does so from `registerWith`) and `FirebasePlatform.isRegistered` allows + checking whether that happened. diff --git a/packages/firebase_core/firebase_core_common/LICENSE b/packages/firebase_core/firebase_core_common/LICENSE new file mode 100644 index 000000000000..000b4618d2bd --- /dev/null +++ b/packages/firebase_core/firebase_core_common/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/packages/firebase_core/firebase_core_common/README.md b/packages/firebase_core/firebase_core_common/README.md new file mode 100644 index 000000000000..6c4fabeb5aa5 --- /dev/null +++ b/packages/firebase_core/firebase_core_common/README.md @@ -0,0 +1,14 @@ +# firebase_core_common + +Pure Dart shared types and platform interfaces used by +[`firebase_core`](https://pub.dev/packages/firebase_core) and its platform +implementations. + +This package contains no Flutter dependency and never imports `package:flutter/*` +(neither directly nor transitively). It exists so that platform implementations +such as [`firebase_core_web`](https://pub.dev/packages/firebase_core_web) can be +used outside of Flutter, for example in pure Dart web applications. + +It is not intended to be used directly by end users. The Flutter facing API is +exposed by `firebase_core`, and `firebase_core_platform_interface` re-exports +this package for backwards compatibility. diff --git a/packages/firebase_core/firebase_core_common/lib/firebase_core_common.dart b/packages/firebase_core/firebase_core_common/lib/firebase_core_common.dart new file mode 100644 index 000000000000..d453defe03bc --- /dev/null +++ b/packages/firebase_core/firebase_core_common/lib/firebase_core_common.dart @@ -0,0 +1,24 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// Pure Dart shared types and platform interfaces for Firebase Core. +/// +/// This library must never import `package:flutter/*` or any library which +/// does, so that Firebase Core platform implementations (such as +/// `firebase_core_web`) can be used outside of Flutter, for example in +/// pure Dart web applications. +/// +/// `firebase_core_platform_interface` re-exports this library, so importing +/// that package (directly or through `firebase_core`) keeps giving access to +/// exactly the same types. +library; + +export 'src/default_firebase_app_name.dart'; +export 'src/firebase_app.dart'; +export 'src/firebase_core_exceptions.dart'; +export 'src/firebase_exception.dart'; +export 'src/firebase_options.dart'; +export 'src/platform_interface/platform_interface_firebase.dart'; +export 'src/platform_interface/platform_interface_firebase_app.dart'; +export 'src/platform_interface/platform_interface_firebase_plugin.dart'; diff --git a/packages/firebase_core/firebase_core_common/lib/src/default_firebase_app_name.dart b/packages/firebase_core/firebase_core_common/lib/src/default_firebase_app_name.dart new file mode 100644 index 000000000000..85120e1cff4c --- /dev/null +++ b/packages/firebase_core/firebase_core_common/lib/src/default_firebase_app_name.dart @@ -0,0 +1,6 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// The default Firebase application name. +const String defaultFirebaseAppName = '[DEFAULT]'; diff --git a/packages/firebase_core/firebase_core/lib/src/firebase_app.dart b/packages/firebase_core/firebase_core_common/lib/src/firebase_app.dart similarity index 86% rename from packages/firebase_core/firebase_core/lib/src/firebase_app.dart rename to packages/firebase_core/firebase_core_common/lib/src/firebase_app.dart index 2dd14a80e0cc..81ce85df9421 100644 --- a/packages/firebase_core/firebase_core/lib/src/firebase_app.dart +++ b/packages/firebase_core/firebase_core_common/lib/src/firebase_app.dart @@ -3,12 +3,22 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of '../firebase_core.dart'; +import 'firebase_options.dart'; +import 'platform_interface/platform_interface_firebase_app.dart'; /// Represents a single Firebase app instance. /// /// You can get an instance by calling [Firebase.app()]. class FirebaseApp { + /// Creates a [FirebaseApp] from a [FirebaseAppPlatform] delegate. + /// + /// This is not intended to be used directly. On Flutter, use + /// `Firebase.initializeApp()` or `Firebase.app()` instead. When using the + /// platform interfaces directly, for example outside of Flutter, wrap the + /// result of [FirebasePlatform.initializeApp] with this constructor. + factory FirebaseApp.fromPlatform(FirebaseAppPlatform delegate) => + FirebaseApp._(delegate); + /// A [FirebaseApp] instance can only be accessed from a call to `app()` on /// [FirebaseCore]. /// diff --git a/packages/firebase_core/firebase_core_platform_interface/lib/src/firebase_core_exceptions.dart b/packages/firebase_core/firebase_core_common/lib/src/firebase_core_exceptions.dart similarity index 96% rename from packages/firebase_core/firebase_core_platform_interface/lib/src/firebase_core_exceptions.dart rename to packages/firebase_core/firebase_core_common/lib/src/firebase_core_exceptions.dart index c231518bf540..8144085912b0 100644 --- a/packages/firebase_core/firebase_core_platform_interface/lib/src/firebase_core_exceptions.dart +++ b/packages/firebase_core/firebase_core_common/lib/src/firebase_core_exceptions.dart @@ -3,7 +3,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of '../firebase_core_platform_interface.dart'; +import 'default_firebase_app_name.dart'; +import 'firebase_exception.dart'; /// Throws a consistent cross-platform error message when usage of an app occurs but /// no app has been created. diff --git a/packages/firebase_core/firebase_core_platform_interface/lib/src/firebase_exception.dart b/packages/firebase_core/firebase_core_common/lib/src/firebase_exception.dart similarity index 97% rename from packages/firebase_core/firebase_core_platform_interface/lib/src/firebase_exception.dart rename to packages/firebase_core/firebase_core_common/lib/src/firebase_exception.dart index 191e96d81a29..506211184b51 100644 --- a/packages/firebase_core/firebase_core_platform_interface/lib/src/firebase_exception.dart +++ b/packages/firebase_core/firebase_core_common/lib/src/firebase_exception.dart @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of '../firebase_core_platform_interface.dart'; +import 'package:meta/meta.dart'; /// A generic class which provides exceptions in a Firebase-friendly format /// to users. diff --git a/packages/firebase_core/firebase_core_platform_interface/lib/src/firebase_options.dart b/packages/firebase_core/firebase_core_common/lib/src/firebase_options.dart similarity index 84% rename from packages/firebase_core/firebase_core_platform_interface/lib/src/firebase_options.dart rename to packages/firebase_core/firebase_core_common/lib/src/firebase_options.dart index 3487164bb4a9..09fbdc8dd2d9 100644 --- a/packages/firebase_core/firebase_core_platform_interface/lib/src/firebase_options.dart +++ b/packages/firebase_core/firebase_core_common/lib/src/firebase_options.dart @@ -3,8 +3,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: deprecated_member_use_from_same_package -part of '../firebase_core_platform_interface.dart'; +import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; /// The options used to configure a Firebase app. /// @@ -53,29 +53,6 @@ class FirebaseOptions { this.appGroupId, }); - /// Named constructor to create [FirebaseOptions] from a the response of Pigeon channel. - /// - /// This constructor is used when platforms cannot directly return a - /// [FirebaseOptions] instance, for example when data is sent back from a - /// [MethodChannel]. - FirebaseOptions.fromPigeon(CoreFirebaseOptions options) - : apiKey = options.apiKey, - appId = options.appId, - messagingSenderId = options.messagingSenderId, - projectId = options.projectId, - authDomain = options.authDomain, - databaseURL = options.databaseURL, - storageBucket = options.storageBucket, - measurementId = options.measurementId, - trackingId = options.trackingId, - deepLinkURLScheme = options.deepLinkURLScheme, - androidClientId = options.androidClientId, - iosClientId = options.iosClientId, - iosBundleId = options.iosBundleId, - appGroupId = options.appGroupId, - recaptchaSiteKey = - ''; // Placeholder to coordinate with fluttefire cli current operation. - /// Returns a copy of this FirebaseOptions with the given fields replaced with /// the new values. FirebaseOptions copyWith({ diff --git a/packages/firebase_core/firebase_core_platform_interface/lib/src/platform_interface/platform_interface_firebase.dart b/packages/firebase_core/firebase_core_common/lib/src/platform_interface/platform_interface_firebase.dart similarity index 50% rename from packages/firebase_core/firebase_core_platform_interface/lib/src/platform_interface/platform_interface_firebase.dart rename to packages/firebase_core/firebase_core_common/lib/src/platform_interface/platform_interface_firebase.dart index 1129b5925a8d..a50d6548db5c 100644 --- a/packages/firebase_core/firebase_core_platform_interface/lib/src/platform_interface/platform_interface_firebase.dart +++ b/packages/firebase_core/firebase_core_common/lib/src/platform_interface/platform_interface_firebase.dart @@ -3,7 +3,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of '../../firebase_core_platform_interface.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../default_firebase_app_name.dart'; +import '../firebase_exception.dart'; +import '../firebase_options.dart'; +import 'platform_interface_firebase_app.dart'; /// The interface that implementations of `firebase_core` must extend. /// @@ -19,15 +24,43 @@ abstract class FirebasePlatform extends PlatformInterface { static final Object _token = Object(); + /// Whether a [FirebasePlatform] implementation has been registered. + /// + /// Implementations register themselves by setting [FirebasePlatform.instance], + /// usually from their `registerWith` method which is called by the Flutter + /// plugin registrant (or manually when running outside of Flutter). + static bool get isRegistered => _instance != null; + /// The default instance of [FirebasePlatform] to use. /// - /// Platform-specific plugins should override this with their own class + /// Platform-specific plugins should set this with their own class /// that extends [FirebasePlatform] when they register themselves. /// - /// Defaults to [MethodChannelFirebase]. - static FirebasePlatform get instance => _instance; + /// Throws a [FirebaseException] if no implementation has been registered yet. + /// On Flutter, `firebase_core` registers its `MethodChannel` implementation + /// before the first usage of the `Firebase` API. Other platforms must call + /// the `registerWith` method of their implementation, for example + /// `FirebaseCoreWeb.registerWith(null)` on web. + static FirebasePlatform get instance { + final FirebasePlatform? instance = _instance; + + if (instance == null) { + throw FirebaseException( + plugin: 'core', + code: 'no-platform-implementation', + message: 'No Firebase Core platform implementation has been ' + 'registered.\n\nIf you are using Flutter, this usually means that ' + 'the platform you are running on is not supported, or that ' + '`Firebase.initializeApp()` has not been called yet.\n\nIf you are ' + 'not using Flutter, register your implementation manually, for ' + 'example by calling `FirebaseCoreWeb.registerWith(null)` on web.', + ); + } + + return instance; + } - static FirebasePlatform _instance = MethodChannelFirebase(); + static FirebasePlatform? _instance; static set instance(FirebasePlatform instance) { PlatformInterface.verify(instance, _token); diff --git a/packages/firebase_core/firebase_core_platform_interface/lib/src/platform_interface/platform_interface_firebase_app.dart b/packages/firebase_core/firebase_core_common/lib/src/platform_interface/platform_interface_firebase_app.dart similarity index 92% rename from packages/firebase_core/firebase_core_platform_interface/lib/src/platform_interface/platform_interface_firebase_app.dart rename to packages/firebase_core/firebase_core_common/lib/src/platform_interface/platform_interface_firebase_app.dart index 6fb7eb59baf6..98a9bd6879d0 100644 --- a/packages/firebase_core/firebase_core_platform_interface/lib/src/platform_interface/platform_interface_firebase_app.dart +++ b/packages/firebase_core/firebase_core_common/lib/src/platform_interface/platform_interface_firebase_app.dart @@ -3,7 +3,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of '../../firebase_core_platform_interface.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../default_firebase_app_name.dart'; +import '../firebase_options.dart'; /// A class storing the name and options of a Firebase app. /// @@ -26,7 +29,7 @@ class FirebaseAppPlatform extends PlatformInterface { final FirebaseOptions options; /// Returns whether this instance is the default Firebase app. - bool get _isDefault => name == defaultFirebaseAppName; + bool get isDefault => name == defaultFirebaseAppName; /// Returns true if automatic data collection is enabled for this app. bool get isAutomaticDataCollectionEnabled { diff --git a/packages/firebase_core/firebase_core_platform_interface/lib/src/platform_interface/platform_interface_firebase_plugin.dart b/packages/firebase_core/firebase_core_common/lib/src/platform_interface/platform_interface_firebase_plugin.dart similarity index 72% rename from packages/firebase_core/firebase_core_platform_interface/lib/src/platform_interface/platform_interface_firebase_plugin.dart rename to packages/firebase_core/firebase_core_common/lib/src/platform_interface/platform_interface_firebase_plugin.dart index 42e470fdc1b6..6a39a41a4121 100644 --- a/packages/firebase_core/firebase_core_platform_interface/lib/src/platform_interface/platform_interface_firebase_plugin.dart +++ b/packages/firebase_core/firebase_core_common/lib/src/platform_interface/platform_interface_firebase_plugin.dart @@ -3,8 +3,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of '../../firebase_core_platform_interface.dart'; - /// The base class that other FlutterFire plugins must extend. /// /// This class provides access to common plugin properties and constants which @@ -21,6 +19,24 @@ abstract class FirebasePlugin { /// requires that the value is synchronously available for use after initialization. static Map _constantsForPluginApps = {}; + /// Registers the plugin [constants] of [appName]. + /// + /// Platform implementations call this once core has been initialized, so + /// that the constants are synchronously available to plugins afterwards. + static void registerConstantsForApp( + String appName, + Map? constants, + ) { + if (constants != null) { + _constantsForPluginApps[appName] = constants; + } + } + + /// Removes the constants registered for the plugins of [appName]. + static void removeConstantsForApp(String appName) { + _constantsForPluginApps.remove(appName); + } + final String _appName; final String _methodChannelName; diff --git a/packages/firebase_core/firebase_core_common/pubspec.yaml b/packages/firebase_core/firebase_core_common/pubspec.yaml new file mode 100644 index 000000000000..479b7b7ba3ac --- /dev/null +++ b/packages/firebase_core/firebase_core_common/pubspec.yaml @@ -0,0 +1,21 @@ +name: firebase_core_common +description: Pure Dart shared types and platform interfaces used by firebase_core + and its platform implementations. +homepage: https://firebase.google.com/docs/flutter/setup +repository: https://github.com/firebase/flutterfire/tree/main/packages/firebase_core/firebase_core_common +version: 0.1.0 +resolution: workspace +topics: + - firebase + - core + +environment: + sdk: '^3.6.0' + +dependencies: + collection: ^1.0.0 + meta: ^1.8.0 + plugin_platform_interface: ^2.1.3 + +dev_dependencies: + test: ^1.25.0 diff --git a/packages/firebase_core/firebase_core_common/test/firebase_core_common_test.dart b/packages/firebase_core/firebase_core_common/test/firebase_core_common_test.dart new file mode 100644 index 000000000000..77ecc0cf4e40 --- /dev/null +++ b/packages/firebase_core/firebase_core_common/test/firebase_core_common_test.dart @@ -0,0 +1,85 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_core_common/firebase_core_common.dart'; +import 'package:test/test.dart'; + +const FirebaseOptions _options = FirebaseOptions( + apiKey: 'api-key', + appId: 'app-id', + messagingSenderId: 'sender-id', + projectId: 'project-id', +); + +void main() { + group('$FirebasePlatform', () { + test('is not registered by default', () { + expect(FirebasePlatform.isRegistered, isFalse); + }); + + test('throws until an implementation is registered', () { + expect( + () => FirebasePlatform.instance, + throwsA( + isA().having( + (error) => error.code, + 'code', + 'no-platform-implementation', + ), + ), + ); + }); + + test('can be set to a platform implementation', () { + FirebasePlatform.instance = _TestFirebasePlatform(); + + expect(FirebasePlatform.isRegistered, isTrue); + expect(FirebasePlatform.instance, isA<_TestFirebasePlatform>()); + }); + }); + + group('$FirebaseApp', () { + test('can be created from a platform delegate', () { + final FirebaseApp app = FirebaseApp.fromPlatform(_TestFirebaseApp()); + + expect(app.name, defaultFirebaseAppName); + expect(app.options, _options); + expect(app.toString(), contains(defaultFirebaseAppName)); + + // The delegate is verified when the app is created, so a platform + // implementation which does not extend `FirebaseAppPlatform` is rejected. + expect( + () => FirebaseApp.fromPlatform(_FakeFirebaseApp()), + throwsA(anything), + ); + }); + }); + + group('$FirebaseException', () { + test('exposes its plugin, code and message', () { + final FirebaseException exception = FirebaseException( + plugin: 'core', + code: 'no-app', + message: 'no app', + ); + + expect(exception.code, 'no-app'); + expect(exception.toString(), contains('no app')); + }); + }); +} + +class _TestFirebasePlatform extends FirebasePlatform {} + +class _TestFirebaseApp extends FirebaseAppPlatform { + _TestFirebaseApp() : super(defaultFirebaseAppName, _options); +} + +// `FirebaseAppPlatform` overrides `==`, which is exactly why implementing it +// (instead of extending it) is rejected when a [FirebaseApp] is created. +// ignore: avoid_implementing_value_types +class _FakeFirebaseApp implements FirebaseAppPlatform { + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} diff --git a/packages/firebase_core/firebase_core_platform_interface/lib/firebase_core_platform_interface.dart b/packages/firebase_core/firebase_core_platform_interface/lib/firebase_core_platform_interface.dart index 5e5fe29248f1..8b5646598463 100644 --- a/packages/firebase_core/firebase_core_platform_interface/lib/firebase_core_platform_interface.dart +++ b/packages/firebase_core/firebase_core_platform_interface/lib/firebase_core_platform_interface.dart @@ -8,20 +8,17 @@ library; import 'dart:async'; -import 'package:collection/collection.dart'; +import 'package:firebase_core_common/firebase_core_common.dart'; import 'package:firebase_core_platform_interface/src/pigeon/messages.pigeon.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -part 'src/firebase_core_exceptions.dart'; -part 'src/firebase_exception.dart'; -part 'src/firebase_options.dart'; +/// The parts of the platform interface which do not depend on Flutter live in +/// `firebase_core_common`, so that they can be consumed outside of Flutter. +/// They are imported by this library (so that the parts below can use them) +/// and re-exported, to keep this package the single entry point for Flutter +/// users. +export 'package:firebase_core_common/firebase_core_common.dart'; + part 'src/method_channel/method_channel_firebase.dart'; part 'src/method_channel/method_channel_firebase_app.dart'; -part 'src/platform_interface/platform_interface_firebase.dart'; -part 'src/platform_interface/platform_interface_firebase_app.dart'; -part 'src/platform_interface/platform_interface_firebase_plugin.dart'; - -/// The default Firebase application name. -const String defaultFirebaseAppName = '[DEFAULT]'; diff --git a/packages/firebase_core/firebase_core_platform_interface/lib/src/method_channel/method_channel_firebase.dart b/packages/firebase_core/firebase_core_platform_interface/lib/src/method_channel/method_channel_firebase.dart index ad3dff75c9de..3c6a53a5cb5e 100644 --- a/packages/firebase_core/firebase_core_platform_interface/lib/src/method_channel/method_channel_firebase.dart +++ b/packages/firebase_core/firebase_core_platform_interface/lib/src/method_channel/method_channel_firebase.dart @@ -38,15 +38,17 @@ class MethodChannelFirebase extends FirebasePlatform { MethodChannelFirebaseApp methodChannelFirebaseApp = MethodChannelFirebaseApp( response.name, - FirebaseOptions.fromPigeon(response.options), + firebaseOptionsFromPigeon(response.options), isAutomaticDataCollectionEnabled: response.isAutomaticDataCollectionEnabled, ); appInstances[methodChannelFirebaseApp.name] = methodChannelFirebaseApp; - FirebasePlugin._constantsForPluginApps[methodChannelFirebaseApp.name] = - response.pluginConstants; + FirebasePlugin.registerConstantsForApp( + methodChannelFirebaseApp.name, + response.pluginConstants, + ); } /// Returns the created [FirebaseAppPlatform] instances. @@ -83,7 +85,7 @@ class MethodChannelFirebase extends FirebasePlatform { defaultApp == null && _options == null) { final options = await api.optionsFromResource(); - _options = FirebaseOptions.fromPigeon(options); + _options = firebaseOptionsFromPigeon(options); } // If no options are present & no default app has been setup, the user is @@ -191,3 +193,30 @@ class MethodChannelFirebase extends FirebasePlatform { throw noAppExists(name); } } + +/// Creates a [FirebaseOptions] from the Pigeon representation used to send the +/// options of a [FirebaseAppPlatform] across a [MethodChannel]. +/// +/// This replaces the `FirebaseOptions.fromPigeon` named constructor, which was +/// removed so that [FirebaseOptions] (exposed by `firebase_core_common`) does +/// not depend on the Flutter dependent Pigeon generated models. +FirebaseOptions firebaseOptionsFromPigeon(CoreFirebaseOptions options) { + return FirebaseOptions( + apiKey: options.apiKey, + appId: options.appId, + messagingSenderId: options.messagingSenderId, + projectId: options.projectId, + authDomain: options.authDomain, + databaseURL: options.databaseURL, + storageBucket: options.storageBucket, + measurementId: options.measurementId, + trackingId: options.trackingId, + deepLinkURLScheme: options.deepLinkURLScheme, + androidClientId: options.androidClientId, + iosClientId: options.iosClientId, + iosBundleId: options.iosBundleId, + appGroupId: options.appGroupId, + // Placeholder to coordinate with the flutterfire CLI current operation. + recaptchaSiteKey: '', + ); +} diff --git a/packages/firebase_core/firebase_core_platform_interface/lib/src/method_channel/method_channel_firebase_app.dart b/packages/firebase_core/firebase_core_platform_interface/lib/src/method_channel/method_channel_firebase_app.dart index 4bfc28642b15..1683b299afa1 100644 --- a/packages/firebase_core/firebase_core_platform_interface/lib/src/method_channel/method_channel_firebase_app.dart +++ b/packages/firebase_core/firebase_core_platform_interface/lib/src/method_channel/method_channel_firebase_app.dart @@ -41,7 +41,7 @@ class MethodChannelFirebaseApp extends FirebaseAppPlatform { /// The default app cannot be deleted. @override Future delete() async { - if (_isDefault) { + if (isDefault) { throw noDefaultAppDelete(); } @@ -52,7 +52,7 @@ class MethodChannelFirebaseApp extends FirebaseAppPlatform { await _api.delete(name); MethodChannelFirebase.appInstances.remove(name); - FirebasePlugin._constantsForPluginApps.remove(name); + FirebasePlugin.removeConstantsForApp(name); _isDeleted = true; } diff --git a/packages/firebase_core/firebase_core_platform_interface/pubspec.yaml b/packages/firebase_core/firebase_core_platform_interface/pubspec.yaml index d481587bd71a..85184605d73a 100644 --- a/packages/firebase_core/firebase_core_platform_interface/pubspec.yaml +++ b/packages/firebase_core/firebase_core_platform_interface/pubspec.yaml @@ -12,7 +12,7 @@ environment: flutter: '>=3.27.0' dependencies: - collection: ^1.0.0 + firebase_core_common: ^0.1.0 flutter: sdk: flutter meta: ^1.8.0 diff --git a/packages/firebase_core/firebase_core_platform_interface/test/firebase_options_test.dart b/packages/firebase_core/firebase_core_platform_interface/test/firebase_options_test.dart index 8b84f78e9525..08ae84c564c5 100644 --- a/packages/firebase_core/firebase_core_platform_interface/test/firebase_options_test.dart +++ b/packages/firebase_core/firebase_core_platform_interface/test/firebase_options_test.dart @@ -48,8 +48,8 @@ void main() { expect(options1 == options2, isFalse); }); - test('should construct an instance from a Map', () { - FirebaseOptions options1 = FirebaseOptions.fromPigeon( + test('should construct an instance from a Pigeon response', () { + FirebaseOptions options1 = firebaseOptionsFromPigeon( CoreFirebaseOptions( apiKey: 'apiKey', appId: 'appId', diff --git a/packages/firebase_core/firebase_core_platform_interface/test/platform_interface_tests/platform_interface_firebase_core_test.dart b/packages/firebase_core/firebase_core_platform_interface/test/platform_interface_tests/platform_interface_firebase_core_test.dart index 5393a22f99d8..7217d5d27d34 100644 --- a/packages/firebase_core/firebase_core_platform_interface/test/platform_interface_tests/platform_interface_firebase_core_test.dart +++ b/packages/firebase_core/firebase_core_platform_interface/test/platform_interface_tests/platform_interface_firebase_core_test.dart @@ -13,8 +13,12 @@ void main() { group('$FirebasePlatform', () { // should allow read of default app from native - test('$MethodChannelFirebase is the default instance', () { + test('$MethodChannelFirebase can be registered as the default instance', + () { + FirebasePlatform.instance = MethodChannelFirebase(); + expect(FirebasePlatform.instance, isA()); + expect(FirebasePlatform.isRegistered, isTrue); }); test('Can be extended', () { diff --git a/packages/firebase_core/firebase_core_web/lib/firebase_core_web.dart b/packages/firebase_core/firebase_core_web/lib/firebase_core_web.dart index ad0a67e97a2f..5e63bff15a7c 100644 --- a/packages/firebase_core/firebase_core_web/lib/firebase_core_web.dart +++ b/packages/firebase_core/firebase_core_web/lib/firebase_core_web.dart @@ -7,7 +7,7 @@ import 'dart:async'; import 'dart:js_interop'; import 'dart:js_interop_unsafe'; -import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; +import 'package:firebase_core_common/firebase_core_common.dart'; import 'package:firebase_core_web/src/firebase_core_version.dart'; import 'package:firebase_core_web/src/interop/package_web_tweaks.dart'; import 'package:firebase_core_web/src/interop/utils/es6_interop.dart'; diff --git a/packages/firebase_core/firebase_core_web/lib/src/interop/core.dart b/packages/firebase_core/firebase_core_web/lib/src/interop/core.dart index 3baa52ceb339..aaa9c556fa57 100644 --- a/packages/firebase_core/firebase_core_web/lib/src/interop/core.dart +++ b/packages/firebase_core/firebase_core_web/lib/src/interop/core.dart @@ -7,7 +7,7 @@ import 'dart:js_interop'; -import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; +import 'package:firebase_core_common/firebase_core_common.dart'; import 'package:firebase_core_web/firebase_core_web_interop.dart'; import 'core_interop.dart' as firebase_interop; diff --git a/packages/firebase_core/firebase_core_web/pubspec.yaml b/packages/firebase_core/firebase_core_web/pubspec.yaml index 4264fabebd19..4bdf6e06d38f 100644 --- a/packages/firebase_core/firebase_core_web/pubspec.yaml +++ b/packages/firebase_core/firebase_core_web/pubspec.yaml @@ -7,10 +7,9 @@ resolution: workspace environment: sdk: '^3.6.0' - flutter: '>=3.22.0' dependencies: - firebase_core_platform_interface: ^8.1.1 + firebase_core_common: ^0.1.0 meta: ^1.8.0 web: ^1.0.0 diff --git a/packages/firebase_core/firebase_core_web/test/firebase_core_web_app_check_multi_app_test.dart b/packages/firebase_core/firebase_core_web/test/firebase_core_web_app_check_multi_app_test.dart index 88bfad5eb7a1..c66acb406b41 100644 --- a/packages/firebase_core/firebase_core_web/test/firebase_core_web_app_check_multi_app_test.dart +++ b/packages/firebase_core/firebase_core_web/test/firebase_core_web_app_check_multi_app_test.dart @@ -5,7 +5,7 @@ @TestOn('browser') library; -import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; +import 'package:firebase_core_common/firebase_core_common.dart'; import 'package:firebase_core_web/firebase_core_web.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/firebase_core/firebase_core_web/test/firebase_core_web_exceptions_test.dart b/packages/firebase_core/firebase_core_web/test/firebase_core_web_exceptions_test.dart index ada205142773..c99f6cfa8d87 100644 --- a/packages/firebase_core/firebase_core_web/test/firebase_core_web_exceptions_test.dart +++ b/packages/firebase_core/firebase_core_web/test/firebase_core_web_exceptions_test.dart @@ -7,7 +7,7 @@ import 'dart:js_interop'; import 'dart:js_interop_unsafe'; -import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; +import 'package:firebase_core_common/firebase_core_common.dart'; import 'package:firebase_core_web/firebase_core_web.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/firebase_core/firebase_core_web/tool/pure_dart_check.dart b/packages/firebase_core/firebase_core_web/tool/pure_dart_check.dart new file mode 100644 index 000000000000..3a84d197a53b --- /dev/null +++ b/packages/firebase_core/firebase_core_web/tool/pure_dart_check.dart @@ -0,0 +1,24 @@ +// Copyright 2026, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// A pure Dart entry point which verifies that this package can be used without +// a Flutter dependency, which is the point of extracting +// `firebase_core_common`. +// +// It is compiled by `melos run check:pure-dart`: +// `dart compile js tool/pure_dart_check.dart` fails as soon as a library which +// depends on Flutter becomes reachable from this package. +import 'package:firebase_core_common/firebase_core_common.dart'; +import 'package:firebase_core_web/firebase_core_web.dart'; + +void main() { + FirebaseCoreWeb.registerWith(null); + + if (!FirebasePlatform.isRegistered) { + throw StateError('The web implementation did not register itself.'); + } + + // ignore: avoid_print + print('firebase_core_web is usable without Flutter.'); +} diff --git a/pubspec.yaml b/pubspec.yaml index e3d6f3195b02..105ac1a8958e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,7 @@ workspace: - packages/firebase_auth/firebase_auth_web - packages/firebase_core/firebase_core - packages/firebase_core/firebase_core/example + - packages/firebase_core/firebase_core_common - packages/firebase_core/firebase_core_platform_interface - packages/firebase_core/firebase_core_web - packages/firebase_crashlytics/firebase_crashlytics @@ -503,6 +504,16 @@ melos: postclean: > melos exec -c 6 -- "flutter clean" + check:pure-dart: + run: | + melos exec --scope=firebase_core_web -- \ + "dart compile js tool/pure_dart_check.dart --output=/tmp/flutterfire_pure_dart_core_check.js" + description: |- + Compile a pure Dart entry point which uses the web implementation of + firebase_core. It fails as soon as a library which depends on Flutter + becomes reachable, which is what allows the web implementation to be + used without Flutter, for example in pure Dart web applications. + add-license-header: # If you add here another --ignore flag, add it also to # "check-license-header".