Skip to content

Commit f550d96

Browse files
committed
fix: a bug with the demoProjectId arg to Firebase.initializeApp()
If platform specific configuration files exist for the project (i.e. GoogleService-Info.plist for iOS), the default `MethodChannelFirebaseApp` is initialized using the configuration it specifies. This is fine since multiple `FirebaseApp` instances can exist, however when `MethodChannelFirebase.initializeApp()` is called it uses the default app name (`[DEFAULT]`, from the code) if no name is passed as an argument. Since the default app is initialized using the platform specific configuration before `initializeApp()` is called, the user provided options are ignored. The solution is to ensure a non-null app name is passed to `initializeApp()` to avoid conflicts with platform specific configuration files when the user intends to use a "demo-" project for testing. Since we should be providing a distinct app name for the demo app, the allowance for mismatched options between the specified options and the existing options for demo apps is also removed.
1 parent 41890d6 commit f550d96

File tree

3 files changed

+62
-14
lines changed

3 files changed

+62
-14
lines changed

packages/firebase_core/firebase_core/lib/src/firebase.dart

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,14 @@ class Firebase {
2828
return _delegate.apps.map(FirebaseApp._).toList(growable: false);
2929
}
3030

31-
/// Initializes a new [FirebaseApp] instance by [name] and [options] and returns
32-
/// the created app. This method should be called before any usage of FlutterFire plugins.
31+
/// Initializes a new [FirebaseApp] instance by [name] and [options] and
32+
/// returns the created app. This method should be called before any usage of
33+
/// FlutterFire plugins.
34+
///
35+
/// If a [demoProjectId] is provided, a new [FirebaseApp] instance will be
36+
/// initialized with a set of default options for demo projects, overriding
37+
/// the [options] argument. If no [name] is provided alongside a
38+
/// [demoProjectId], the [demoProjectId] will be used as the app name.
3339
///
3440
/// The default app instance can be initialized here simply by passing no "name" as an argument
3541
/// in both Dart & manual initialization flows.
@@ -52,16 +58,21 @@ class Firebase {
5258
// We use 'web' as the default platform for unknown platforms.
5359
platformString = 'web';
5460
}
55-
FirebaseAppPlatform app = await _delegate.initializeApp(
56-
options: FirebaseOptions(
57-
apiKey: '',
58-
appId: '1:1:$platformString:1',
59-
messagingSenderId: '',
60-
projectId: demoProjectId,
61-
),
61+
// A name must be set, otherwise [DEFAULT] will be used and the options
62+
// we've provided will be ignored if any platform specific configuration
63+
// files exist (i.e. GoogleService-Info.plist for iOS).
64+
name ??= demoProjectId;
65+
// The user should not set any options if they specify a demo project
66+
// id, but it was allowed when this API was first added, so we allow it
67+
// for backwards compatibility and simply override the user-provided
68+
// options.
69+
options = FirebaseOptions(
70+
apiKey: '',
71+
appId: '1:1:$platformString:1',
72+
messagingSenderId: '',
73+
projectId: demoProjectId,
6274
);
63-
64-
return FirebaseApp._(app);
75+
// Now fall through to the normal initialization logic.
6576
}
6677
FirebaseAppPlatform app = await _delegate.initializeApp(
6778
name: name,

packages/firebase_core/firebase_core/test/firebase_core_test.dart

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,45 @@ void main() {
6565
]);
6666
});
6767
});
68+
69+
test('.initializeApp() with demoProjectId', () async {
70+
const String demoProjectId = 'demo-project-id';
71+
const String expectedName = demoProjectId;
72+
const FirebaseOptions expectedOptions = FirebaseOptions(
73+
apiKey: '',
74+
// Flutter tests use android as the default platform.
75+
appId: '1:1:android:1',
76+
messagingSenderId: '',
77+
projectId: demoProjectId,
78+
);
79+
80+
final mock = MockFirebaseCore();
81+
Firebase.delegatePackingProperty = mock;
82+
83+
final FirebaseAppPlatform platformApp =
84+
FirebaseAppPlatform(expectedName, expectedOptions);
85+
86+
when(mock.apps).thenReturn([platformApp]);
87+
when(mock.app(expectedName)).thenReturn(platformApp);
88+
when(mock.initializeApp(name: expectedName, options: expectedOptions))
89+
.thenAnswer((_) => Future.value(platformApp));
90+
91+
// Initialize the app with only a demo project id. The implementation will
92+
// set the name and options accordingly.
93+
FirebaseApp initializedApp = await Firebase.initializeApp(
94+
demoProjectId: demoProjectId,
95+
);
96+
FirebaseApp app = Firebase.app(expectedName);
97+
98+
expect(initializedApp, app);
99+
verifyInOrder([
100+
mock.initializeApp(
101+
name: expectedName,
102+
options: expectedOptions,
103+
),
104+
mock.app(expectedName),
105+
]);
106+
});
68107
}
69108

70109
class MockFirebaseCore extends Mock

packages/firebase_core/firebase_core_platform_interface/lib/src/method_channel/method_channel_firebase.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,7 @@ class MethodChannelFirebase extends FirebasePlatform {
121121
// check to see if options are roughly identical (so we don't unnecessarily
122122
// throw on minor differences such as platform specific keys missing
123123
// e.g. hot reloads/restarts).
124-
if (defaultApp != null &&
125-
_options != null &&
126-
!_options.projectId.startsWith('demo-')) {
124+
if (defaultApp != null && _options != null) {
127125
if (_options.apiKey != defaultApp.options.apiKey ||
128126
(_options.databaseURL != null &&
129127
_options.databaseURL != defaultApp.options.databaseURL) ||

0 commit comments

Comments
 (0)