diff --git a/README.md b/README.md index c678276a..e0193612 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,11 @@ If you wish to be specific about which platforms you want to configure, use the flutterfire configure --yes --project= --platforms=android,ios,web ``` +If you want to specify a display name (i.e. how it appears in the Firebase console), use the `--display-name` flag. It will still have the platform as a suffix (e.g. `--display-name=test` will appear as "test (ios), "test (web)", etc"): +```bash +flutterfire configure --yes --project= --display-name=test +``` + FlutterFire CLI now supports Windows applications. The Firebase console does not allow you to create an application for Windows, FlutterFire CLI will create a **web app** if you specify windows as a platform you want to configure: ```bash diff --git a/packages/flutterfire_cli/lib/src/commands/config.dart b/packages/flutterfire_cli/lib/src/commands/config.dart index 5b2b5d19..92d3078b 100644 --- a/packages/flutterfire_cli/lib/src/commands/config.dart +++ b/packages/flutterfire_cli/lib/src/commands/config.dart @@ -62,6 +62,14 @@ class ConfigCommand extends FlutterFireCommand { 'Optionally specify the platforms to generate configuration options for ' 'as a comma separated list. For example "android,ios,macos,web,linux,windows".', ); + argParser.addOption( + kDisplayNameFlag, + valueHelp: 'displayName', + abbr: 'n', + help: 'The display name of your app, e.g. "My Cool App". ' + 'If no display name is provided then an attempt will be made to ' + 'automatically detect it from your project configuration (if it exists).', + ); argParser.addOption( kIosBundleIdFlag, valueHelp: 'bundleIdentifier', @@ -243,6 +251,11 @@ class ConfigCommand extends FlutterFireCommand { .toList(); } + String get displayName { + final name = argResults![kDisplayNameFlag] as String?; + return name ?? flutterApp!.package.pubSpec.name; + } + bool get applyGradlePlugins { return argResults!['apply-gradle-plugins'] as bool; } @@ -625,6 +638,7 @@ class ConfigCommand extends FlutterFireCommand { iosBundleId: iosBundleId, macosBundleId: macosBundleId, token: token, + displayName: displayName, serviceAccount: serviceAccount, webAppId: webAppId, windowsAppId: windowsAppId, diff --git a/packages/flutterfire_cli/lib/src/common/utils.dart b/packages/flutterfire_cli/lib/src/common/utils.dart index b99caf47..169aacab 100644 --- a/packages/flutterfire_cli/lib/src/common/utils.dart +++ b/packages/flutterfire_cli/lib/src/common/utils.dart @@ -63,6 +63,7 @@ const String kConfigurations = 'configurations'; const String kOutFlag = 'out'; const String kYesFlag = 'yes'; const String kPlatformsFlag = 'platforms'; +const String kDisplayNameFlag = 'display-name'; const String kIosBundleIdFlag = 'ios-bundle-id'; const String kMacosBundleIdFlag = 'macos-bundle-id'; const String kAndroidAppIdFlag = 'android-app-id'; diff --git a/packages/flutterfire_cli/lib/src/firebase/firebase_android_options.dart b/packages/flutterfire_cli/lib/src/firebase/firebase_android_options.dart index a66e7c99..aeadeb30 100644 --- a/packages/flutterfire_cli/lib/src/firebase/firebase_android_options.dart +++ b/packages/flutterfire_cli/lib/src/firebase/firebase_android_options.dart @@ -40,6 +40,7 @@ extension FirebaseAndroidOptions on FirebaseOptions { String? firebaseAccount, required String? token, required String? serviceAccount, + required String displayName, }) async { var selectedAndroidApplicationId = androidApplicationId ?? flutterApp.androidApplicationId; @@ -56,7 +57,7 @@ extension FirebaseAndroidOptions on FirebaseOptions { ); final firebaseApp = await firebase.findOrCreateFirebaseApp( packageNameOrBundleIdentifier: selectedAndroidApplicationId, - displayName: flutterApp.package.pubSpec.name, + displayName: displayName, platform: kAndroid, project: firebaseProjectId, account: firebaseAccount, diff --git a/packages/flutterfire_cli/lib/src/firebase/firebase_apple_options.dart b/packages/flutterfire_cli/lib/src/firebase/firebase_apple_options.dart index 36debd88..d6fde9ce 100644 --- a/packages/flutterfire_cli/lib/src/firebase/firebase_apple_options.dart +++ b/packages/flutterfire_cli/lib/src/firebase/firebase_apple_options.dart @@ -33,6 +33,7 @@ extension FirebaseAppleOptions on FirebaseOptions { String? firebaseAccount, required String? token, required String? serviceAccount, + required String displayName, }) async { final platformIdentifier = macos ? kMacos : kIos; var selectedAppleBundleId = appleBundleIdentifier ?? @@ -58,7 +59,7 @@ extension FirebaseAppleOptions on FirebaseOptions { ); final firebaseApp = await firebase.findOrCreateFirebaseApp( packageNameOrBundleIdentifier: selectedAppleBundleId, - displayName: flutterApp.package.pubSpec.name, + displayName: displayName, platform: platformIdentifier, project: firebaseProjectId, account: firebaseAccount, diff --git a/packages/flutterfire_cli/lib/src/firebase/firebase_dart_options.dart b/packages/flutterfire_cli/lib/src/firebase/firebase_dart_options.dart index edadd712..3526e586 100644 --- a/packages/flutterfire_cli/lib/src/firebase/firebase_dart_options.dart +++ b/packages/flutterfire_cli/lib/src/firebase/firebase_dart_options.dart @@ -32,9 +32,10 @@ extension FirebaseDartOptions on FirebaseOptions { String platform = kWeb, required String? token, required String? serviceAccount, + required String displayName, }) async { final firebaseApp = await firebase.findOrCreateFirebaseApp( - displayName: flutterApp.package.pubSpec.name, + displayName: displayName, platform: platform, project: firebaseProjectId, account: firebaseAccount, diff --git a/packages/flutterfire_cli/lib/src/firebase/firebase_platform_options.dart b/packages/flutterfire_cli/lib/src/firebase/firebase_platform_options.dart index 1a9d50b4..09d063c2 100644 --- a/packages/flutterfire_cli/lib/src/firebase/firebase_platform_options.dart +++ b/packages/flutterfire_cli/lib/src/firebase/firebase_platform_options.dart @@ -40,6 +40,7 @@ Future fetchAllFirebaseOptions({ String? windowsAppId, String? token, String? serviceAccount, + required String displayName, }) async { FirebaseOptions? androidOptions; FirebaseOptions? iosOptions; @@ -56,6 +57,7 @@ Future fetchAllFirebaseOptions({ firebaseAccount: firebaseAccount, token: token, serviceAccount: serviceAccount, + displayName: displayName, ); } @@ -67,6 +69,7 @@ Future fetchAllFirebaseOptions({ firebaseAccount: firebaseAccount, token: token, serviceAccount: serviceAccount, + displayName: displayName, ); } if (macos) { @@ -78,6 +81,7 @@ Future fetchAllFirebaseOptions({ macos: true, token: token, serviceAccount: serviceAccount, + displayName: displayName, ); } @@ -89,6 +93,7 @@ Future fetchAllFirebaseOptions({ webAppId: webAppId, token: token, serviceAccount: serviceAccount, + displayName: displayName, ); } @@ -101,6 +106,7 @@ Future fetchAllFirebaseOptions({ token: token, webAppId: windowsAppId, serviceAccount: serviceAccount, + displayName: displayName, ); } @@ -112,6 +118,7 @@ Future fetchAllFirebaseOptions({ platform: kLinux, token: token, serviceAccount: serviceAccount, + displayName: displayName, ); } diff --git a/packages/flutterfire_cli/test/test_utils.dart b/packages/flutterfire_cli/test/test_utils.dart index 606b7891..5a31e9f6 100644 --- a/packages/flutterfire_cli/test/test_utils.dart +++ b/packages/flutterfire_cli/test/test_utils.dart @@ -55,6 +55,39 @@ Future createFlutterProject() async { final flutterProjectPath = p.join(tempDir.path, flutterProject); + // Set iOS minimum deployment target to 15.0 + const iosVersion = '15.0'; + + // Update project.pbxproj + final pbxprojResult = await Process.run( + 'sed', + [ + '-i', + '', + 's/IPHONEOS_DEPLOYMENT_TARGET = [0-9.]*;/IPHONEOS_DEPLOYMENT_TARGET = $iosVersion;/', + 'ios/Runner.xcodeproj/project.pbxproj', + ], + workingDirectory: flutterProjectPath, + ); + + if (pbxprojResult.exitCode != 0) { + throw Exception( + 'Failed to set iOS deployment target: ${pbxprojResult.stderr}', + ); + } + + // Update Podfile if it exists + final podfilePath = p.join(flutterProjectPath, 'ios', 'Podfile'); + final podfile = File(podfilePath); + if (podfile.existsSync()) { + var podfileContent = podfile.readAsStringSync(); + podfileContent = podfileContent.replaceFirst( + RegExp("platform :ios, '[0-9.]+'"), + "platform :ios, '$iosVersion'", + ); + podfile.writeAsStringSync(podfileContent); + } + return flutterProjectPath; } diff --git a/packages/flutterfire_cli/test/unit_test.dart b/packages/flutterfire_cli/test/unit_test.dart index 292473d8..a804d6d2 100644 --- a/packages/flutterfire_cli/test/unit_test.dart +++ b/packages/flutterfire_cli/test/unit_test.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutterfire_cli/src/common/strings.dart'; import 'package:flutterfire_cli/src/common/utils.dart'; +import 'package:flutterfire_cli/src/firebase/firebase_app.dart'; import 'package:test/test.dart'; void main() { @@ -142,6 +143,49 @@ void main() { }); }); + group('FirebaseApp displayName handling', () { + test('FirebaseApp.fromJson correctly parses displayName from JSON', () { + final json = { + 'platform': 'ANDROID', + 'appId': '1:123456:android:abc', + 'displayName': 'My Cool App', + 'name': 'projects/test/apps/abc', + 'packageName': 'com.example.app', + }; + + final app = FirebaseApp.fromJson(json); + + expect(app.displayName, 'My Cool App'); + expect(app.name, 'projects/test/apps/abc'); + expect(app.platform, 'android'); + expect(app.packageNameOrBundleIdentifier, 'com.example.app'); + }); + + test('FirebaseApp.fromJson handles null or missing displayName', () { + // Test with null displayName + final jsonWithNull = { + 'platform': 'IOS', + 'appId': '1:123456:ios:xyz', + 'displayName': null, + 'name': 'projects/test/apps/xyz', + 'bundleId': 'com.example.app', + }; + + final appWithNull = FirebaseApp.fromJson(jsonWithNull); + expect(appWithNull.displayName, null); + + // Test with missing displayName field + final jsonWithoutField = { + 'platform': 'WEB', + 'appId': '1:123456:web:def', + 'name': 'projects/test/apps/def', + }; + + final appWithoutField = FirebaseApp.fromJson(jsonWithoutField); + expect(appWithoutField.displayName, null); + }); + }); + group( 'Firebase CLI JSON response parser function `firebaseCLIJsonParse()`', () {