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
5 changes: 3 additions & 2 deletions packages/firebase_ai/firebase_ai/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

import 'package:firebase_ai/firebase_ai.dart';

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -70,10 +71,10 @@ class _GenerativeAISampleState extends State<GenerativeAISample> {

void _initializeModel(bool useVertexBackend) {
if (useVertexBackend) {
final vertexInstance = FirebaseAI.vertexAI(auth: FirebaseAuth.instance);
final vertexInstance = FirebaseAI.vertexAI();
_currentModel = vertexInstance.generativeModel(model: 'gemini-2.5-flash');
} else {
final googleAI = FirebaseAI.googleAI(auth: FirebaseAuth.instance);
final googleAI = FirebaseAI.googleAI();
_currentModel = googleAI.generativeModel(model: 'gemini-2.5-flash');
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:firebase_ai/firebase_ai.dart';
import '../widgets/message_widget.dart';
Expand Down Expand Up @@ -57,12 +56,12 @@ class _ChatPageState extends State<ChatPage> {
: null,
);
if (widget.useVertexBackend) {
_model = FirebaseAI.vertexAI(auth: FirebaseAuth.instance).generativeModel(
_model = FirebaseAI.vertexAI().generativeModel(
model: 'gemini-2.5-flash',
generationConfig: generationConfig,
);
} else {
_model = FirebaseAI.googleAI(auth: FirebaseAuth.instance).generativeModel(
_model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-2.5-flash',
generationConfig: generationConfig,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import 'package:flutter/material.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_auth/firebase_auth.dart';

import '../utils/function_call_utils.dart';
import '../widgets/message_widget.dart';

Expand Down Expand Up @@ -235,9 +235,8 @@ class _FunctionCallingPageState extends State<FunctionCallingPage> {
: null,
);

final aiClient = widget.useVertexBackend
? FirebaseAI.vertexAI(auth: FirebaseAuth.instance)
: FirebaseAI.googleAI(auth: FirebaseAuth.instance);
final aiClient =
widget.useVertexBackend ? FirebaseAI.vertexAI() : FirebaseAI.googleAI();

_functionCallModel = aiClient.generativeModel(
model: 'gemini-2.5-flash',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:firebase_ai/firebase_ai.dart';
import '../widgets/message_widget.dart';
Expand Down Expand Up @@ -74,9 +73,8 @@ class _GroundingPageState extends State<GroundingPage> {
}
}

final aiProvider = widget.useVertexBackend
? FirebaseAI.vertexAI(auth: FirebaseAuth.instance)
: FirebaseAI.googleAI(auth: FirebaseAuth.instance);
final aiProvider =
widget.useVertexBackend ? FirebaseAI.vertexAI() : FirebaseAI.googleAI();

_model = aiProvider.generativeModel(
model: 'gemini-2.5-flash',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_auth/firebase_auth.dart';

import '../widgets/message_widget.dart';

class ImageGenerationPage extends StatefulWidget {
Expand Down Expand Up @@ -47,9 +47,8 @@ class _ImageGenerationPageState extends State<ImageGenerationPage> {
}

void _initializeModel() {
final aiClient = widget.useVertexBackend
? FirebaseAI.vertexAI(auth: FirebaseAuth.instance)
: FirebaseAI.googleAI(auth: FirebaseAuth.instance);
final aiClient =
widget.useVertexBackend ? FirebaseAI.vertexAI() : FirebaseAI.googleAI();

_model = aiClient.generativeModel(
model: 'gemini-2.5-flash-image',
Expand Down
14 changes: 9 additions & 5 deletions packages/firebase_ai/firebase_ai/lib/src/base_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -282,19 +282,23 @@ abstract class BaseModel {
) {
return () async {
Map<String, String> headers = {};

final effectiveAppCheck = appCheck ?? app?.getService<FirebaseAppCheck>();
final effectiveAuth = auth ?? app?.getService<FirebaseAuth>();

// Override the client name in Google AI SDK
headers['x-goog-api-client'] =
'gl-dart/$packageVersion fire/$packageVersion';
if (appCheck != null) {
if (effectiveAppCheck != null) {
final appCheckToken = useLimitedUseAppCheckTokens == true
? await appCheck.getLimitedUseToken()
: await appCheck.getToken();
? await effectiveAppCheck.getLimitedUseToken()
: await effectiveAppCheck.getToken();
if (appCheckToken != null) {
headers['X-Firebase-AppCheck'] = appCheckToken;
}
}
if (auth != null) {
final idToken = await auth.currentUser?.getIdToken();
if (effectiveAuth != null) {
final idToken = await effectiveAuth.currentUser?.getIdToken();
if (idToken != null) {
headers['Authorization'] = 'Firebase $idToken';
}
Expand Down
12 changes: 12 additions & 0 deletions packages/firebase_ai/firebase_ai/lib/src/firebase_ai.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,18 @@ class FirebaseAI extends FirebasePluginPlatform {
/// If pass in [appCheck], request session will get protected from abusing.
static FirebaseAI vertexAI({
FirebaseApp? app,
@Deprecated(
'Passing an explicit instance is deprecated, internal handling is now automatic.')
FirebaseAppCheck? appCheck,
@Deprecated(
'Passing an explicit instance is deprecated, internal handling is now automatic.')
FirebaseAuth? auth,
String? location,
bool? useLimitedUseAppCheckTokens,
}) {
app ??= Firebase.app();
appCheck ??= app.getService<FirebaseAppCheck>();
auth ??= app.getService<FirebaseAuth>();
var instanceKey = '${app.name}::vertexai::$location';

if (_cachedInstances.containsKey(instanceKey)) {
Expand Down Expand Up @@ -95,11 +101,17 @@ class FirebaseAI extends FirebasePluginPlatform {
/// If pass in [appCheck], request session will get protected from abusing.
static FirebaseAI googleAI({
FirebaseApp? app,
@Deprecated(
'Passing an explicit instance is deprecated, internal handling is now automatic.')
FirebaseAppCheck? appCheck,
@Deprecated(
'Passing an explicit instance is deprecated, internal handling is now automatic.')
FirebaseAuth? auth,
bool? useLimitedUseAppCheckTokens,
}) {
app ??= Firebase.app();
appCheck ??= app.getService<FirebaseAppCheck>();
auth ??= app.getService<FirebaseAuth>();
var instanceKey = '${app.name}::googleai';

if (_cachedInstances.containsKey(instanceKey)) {
Expand Down
17 changes: 17 additions & 0 deletions packages/firebase_ai/firebase_ai/test/firebase_vertexai_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_app_check/firebase_app_check.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_test/flutter_test.dart';

Expand All @@ -28,6 +29,7 @@ void main() {
late FirebaseApp customApp;
late FirebaseApp limitTokenApp;
late FirebaseAppCheck customAppCheck;
late FirebaseAuth customAuth;
late FirebaseAppCheck limitTokenAppCheck;

group('FirebaseAI Tests', () {
Expand All @@ -47,6 +49,7 @@ void main() {
appCheck = FirebaseAppCheck.instance;
customAppCheck = FirebaseAppCheck.instanceFor(app: customApp);
limitTokenAppCheck = FirebaseAppCheck.instanceFor(app: limitTokenApp);
customAuth = FirebaseAuth.instanceFor(app: customApp);
});

test('Singleton behavior', () {
Expand Down Expand Up @@ -96,6 +99,20 @@ void main() {
expect(vertexAIAppCheck.useLimitedUseAppCheckTokens, true);
});

test('Instance creation with auto-injected AppCheck', () {
final vertexAI = FirebaseAI.vertexAI(app: customApp);

expect(vertexAI.app, equals(customApp));
expect(vertexAI.appCheck, equals(customAppCheck));
});

test('Instance creation with auto-injected Auth', () {
final vertexAI = FirebaseAI.vertexAI(app: customApp);

expect(vertexAI.app, equals(customApp));
expect(vertexAI.auth, equals(customAuth));
});

test('generativeModel creation with Grounding tools', () {
final ai = FirebaseAI.googleAI();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

part of '../firebase_app_check.dart';

class FirebaseAppCheck extends FirebasePluginPlatform {
class FirebaseAppCheck extends FirebasePluginPlatform
implements FirebaseService {
static Map<String, FirebaseAppCheck> _firebaseAppCheckInstances = {};

FirebaseAppCheck._({required this.app})
Expand Down Expand Up @@ -41,7 +42,9 @@ class FirebaseAppCheck extends FirebasePluginPlatform {
/// Returns an instance using a specified [FirebaseApp].
static FirebaseAppCheck instanceFor({required FirebaseApp app}) {
return _firebaseAppCheckInstances.putIfAbsent(app.name, () {
return FirebaseAppCheck._(app: app);
final instance = FirebaseAppCheck._(app: app);
app.registerService<FirebaseAppCheck>(instance);
return instance;
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
part of '../firebase_auth.dart';

/// The entry point of the Firebase Authentication SDK.
class FirebaseAuth extends FirebasePluginPlatform {
class FirebaseAuth extends FirebasePluginPlatform implements FirebaseService {
// Cached instances of [FirebaseAuth].
static Map<String, FirebaseAuth> _firebaseAuthInstances = {};

Expand Down Expand Up @@ -45,7 +45,9 @@ class FirebaseAuth extends FirebasePluginPlatform {
required FirebaseApp app,
}) {
return _firebaseAuthInstances.putIfAbsent(app.name, () {
return FirebaseAuth._(app: app);
final instance = FirebaseAuth._(app: app);
app.registerService<FirebaseAuth>(instance);
return instance;
});
}

Expand Down
21 changes: 21 additions & 0 deletions packages/firebase_core/firebase_core/lib/src/firebase_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class FirebaseApp {
/// Deleting the default app is not possible and throws an exception.
Future<void> delete() async {
await _delegate.delete();
_registries.remove(name);
}

/// The name of this [FirebaseApp].
Expand Down Expand Up @@ -71,4 +72,24 @@ class FirebaseApp {

@override
String toString() => '$FirebaseApp($name)';

static final Map<String, Map<Type, dynamic>> _registries = {};
Comment thread
cynthiajoan marked this conversation as resolved.

/// Registers a service instance for this app.
void registerService<T extends FirebaseService>(T service) {
final registry = _registries.putIfAbsent(name, () => {});
registry[T] = service;
}

/// Returns a registered service instance for this app.
T? getService<T extends FirebaseService>() {
final registry = _registries[name];
if (registry != null) {
return registry[T] as T?;
}
return null;
}
Comment on lines +85 to +91
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

The getService method can be simplified using Dart's null-aware operators for better conciseness and readability.

  T? getService<T extends FirebaseService>() {
    return _registries[name]?[T] as T?;
  }

}

/// A marker interface for Firebase services that can be registered in [FirebaseApp].
abstract class FirebaseService {}
11 changes: 11 additions & 0 deletions packages/firebase_core/firebase_core/test/firebase_core_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ void main() {
mock.app(testAppName),
]);
});

test('.registerService() and .getService()', () {
FirebaseApp app = Firebase.app(testAppName);

final testService = TestService();
app.registerService<TestService>(testService);

expect(app.getService<TestService>(), testService);
});
});

test('.initializeApp() with demoProjectId', () async {
Expand Down Expand Up @@ -152,3 +161,5 @@ class MockFirebaseCore extends Mock

// ignore: avoid_implementing_value_types
class FakeFirebaseAppPlatform extends Fake implements FirebaseAppPlatform {}

class TestService implements FirebaseService {}
Loading