Skip to content
Closed
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
106 changes: 87 additions & 19 deletions packages/flutterfire_cli/lib/src/commands/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,12 @@ class ConfigCommand extends FlutterFireCommand {
return 'android';
}

Future<FirebaseProject> _promptCreateFirebaseProject() async {
Future<FirebaseProject> _promptCreateFirebaseProject({String? flavor}) async {
final forFlavorKeyword = flavor != null ? 'for $flavor' : '';
final flavorKeyword = flavor != null ? '-$flavor' : '';

final newProjectId = promptInput(
'Enter a project id for your new Firebase project (e.g. ${AnsiStyles.cyan('my-cool-project')})',
'Enter a project id for your new Firebase project (e.g. ${AnsiStyles.cyan('my-cool-project$flavorKeyword')})',
validator: (String x) {
if (RegExp(r'^[a-zA-Z0-9\-]+$').hasMatch(x)) {
return true;
Expand All @@ -238,7 +241,7 @@ class ConfigCommand extends FlutterFireCommand {
final creatingProjectSpinner = spinner(
(done) {
if (!done) {
return 'Creating new Firebase project ${AnsiStyles.cyan(newProjectId)}...';
return 'Creating new Firebase project ${AnsiStyles.cyan(newProjectId)} $forFlavorKeyword...';
}
return 'New Firebase project ${AnsiStyles.cyan(newProjectId)} created successfully.';
},
Expand All @@ -252,7 +255,7 @@ class ConfigCommand extends FlutterFireCommand {
return newProject;
}

Future<FirebaseProject> _selectFirebaseProject() async {
Future<FirebaseProject> _selectFirebaseProject({String? flavor}) async {
var selectedProjectId = projectId;
selectedProjectId ??= await firebase.getDefaultFirebaseProjectId();

Expand All @@ -261,6 +264,7 @@ class ConfigCommand extends FlutterFireCommand {
}

List<FirebaseProject>? firebaseProjects;
final flavorKeyword = flavor != null ? 'for $flavor' : '';

final fetchingProjectsSpinner = spinner(
(done) {
Expand All @@ -270,7 +274,7 @@ class ConfigCommand extends FlutterFireCommand {
final baseMessage =
'Found ${AnsiStyles.cyan('${firebaseProjects?.length ?? 0}')} Firebase projects.';
if (selectedProjectId != null) {
return '$baseMessage Selecting project ${AnsiStyles.cyan(selectedProjectId)}.';
return '$baseMessage Selecting project ${AnsiStyles.cyan(selectedProjectId)} $flavorKeyword.';
}
return baseMessage;
},
Expand Down Expand Up @@ -304,13 +308,13 @@ class ConfigCommand extends FlutterFireCommand {
];

final selectedChoiceIndex = promptSelect(
'Select a Firebase project to configure your Flutter application with',
'Select a Firebase project to configure your Flutter application with, $flavorKeyword',
choices,
);

// Last choice is to create a new project.
if (selectedChoiceIndex == choices.length - 1) {
return _promptCreateFirebaseProject();
return _promptCreateFirebaseProject(flavor: flavor);
}

return firebaseProjects[selectedChoiceIndex];
Expand Down Expand Up @@ -365,7 +369,67 @@ class ConfigCommand extends FlutterFireCommand {
Future<void> run() async {
commandRequiresFlutterApp();

final selectedFirebaseProject = await _selectFirebaseProject();
var supportFlavors = false;
if (flutterApp!.hasFlavors) {
supportFlavors = promptBool(
'Do you want to create a Firebase project for each flavor?',
defaultValue: supportFlavors,
);
}

// since user choose to support flavor then
// user can select a different firebase project for each flavor.

if (supportFlavors) {
// the only things allows to be different
// is ios bundle id and android app id.
assert(
flutterApp!.iosFlavors?.length == flutterApp!.androidFlavors?.length,
'ios and android flavors must be same length',
);
assert(
flutterApp!.iosFlavors?.keys.join() ==
flutterApp!.androidFlavors?.keys.join(),
'ios and android flavors must be same',
);
final flavors = flutterApp?.iosFlavors?.keys ?? [];
final androidFlavors = flutterApp?.androidFlavors;
final iosFlavors = flutterApp?.iosFlavors;
// or android flavors since they are same.

for (final key in flavors) {
await start(
flavor: key,
androidApplicationId: androidFlavors?[key],
iosBundleId: iosFlavors?[key],
);
}

// add shell script to xcode project to switch google-service-info.plist file based on current flavor.
final xcodeProjFilePath =
path.join(flutterApp!.iosDirectory.path, 'Runner.xcodeproj');
final rubyScript = buildSettingsRubyScript(xcodeProjFilePath);
if (Platform.isMacOS) {
await Process.run('ruby', ['-e', rubyScript]);
}
} else {
// if user doesn't want to support flavor then
// we can use the same firebase project for all flavors.
await start();
}
logger.stdout('');
logger.stdout(
logLearnMoreAboutCli,
);
}

Future<void> start({
String? flavor,
String? androidApplicationId,
String? iosBundleId,
}) async {
final selectedFirebaseProject =
await _selectFirebaseProject(flavor: flavor);
final selectedPlatforms = _selectPlatforms();

if (!selectedPlatforms.containsValue(true)) {
Expand All @@ -376,7 +440,7 @@ class ConfigCommand extends FlutterFireCommand {
if (selectedPlatforms[kAndroid]!) {
androidOptions = await FirebaseAndroidOptions.forFlutterApp(
flutterApp!,
androidApplicationId: androidApplicationId,
androidApplicationId: androidApplicationId ?? this.androidApplicationId,
firebaseProjectId: selectedFirebaseProject.projectId,
firebaseAccount: accountEmail,
token: token,
Expand All @@ -387,7 +451,7 @@ class ConfigCommand extends FlutterFireCommand {
if (selectedPlatforms[kIos]!) {
iosOptions = await FirebaseAppleOptions.forFlutterApp(
flutterApp!,
appleBundleIdentifier: iosBundleId,
appleBundleIdentifier: iosBundleId ?? this.iosBundleId,
firebaseProjectId: selectedFirebaseProject.projectId,
firebaseAccount: accountEmail,
token: token,
Expand Down Expand Up @@ -440,8 +504,11 @@ class ConfigCommand extends FlutterFireCommand {

final futures = <Future>[];

final mOutputFilePath = flavor != null
? '${outputFilePath.replaceFirst('.dart', '')}_$flavor.dart'
: outputFilePath;
final configFile = FirebaseConfigurationFile(
outputFilePath,
mOutputFilePath,
androidOptions: androidOptions,
iosOptions: iosOptions,
macosOptions: macosOptions,
Expand All @@ -455,8 +522,8 @@ class ConfigCommand extends FlutterFireCommand {
if (generateAppIdJson) {
if (iosOptions != null) {
final appIDFile = FirebaseAppIDFile(
// In order to generate if we're not in the folder of the flutterApp
flutterApp?.iosDirectory.path ?? iosAppIDOutputFilePrefix,
flavor: flavor,
options: iosOptions,
force: isCI || yes,
);
Expand Down Expand Up @@ -484,6 +551,7 @@ class ConfigCommand extends FlutterFireCommand {
flutterApp!,
androidOptions,
logger,
flavor: flavor,
).apply(force: isCI || yes),
);
}
Expand All @@ -492,20 +560,24 @@ class ConfigCommand extends FlutterFireCommand {
final googleServiceInfoFile = path.join(
flutterApp!.iosDirectory.path,
'Runner',
flavor ?? '',
iosOptions.optionsSourceFileName,
);

final file = File(googleServiceInfoFile);

if (!file.existsSync()) {
await file.create(recursive: true);
await file.writeAsString(iosOptions.optionsSourceContent);
}

final xcodeProjFilePath =
path.join(flutterApp!.iosDirectory.path, 'Runner.xcodeproj');

final rubyScript =
generateRubyScript(googleServiceInfoFile, xcodeProjFilePath);
final rubyScript = generateRubyScript(
googleServiceInfoFile,
xcodeProjFilePath,
flavor: flavor,
);

if (Platform.isMacOS) {
final result = await Process.run('ruby', [
Expand Down Expand Up @@ -570,9 +642,5 @@ class ConfigCommand extends FlutterFireCommand {
paddingSize: 2,
),
);
logger.stdout('');
logger.stdout(
logLearnMoreAboutCli,
);
}
}
64 changes: 52 additions & 12 deletions packages/flutterfire_cli/lib/src/common/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
*/

import 'dart:io';

import 'package:ansi_styles/ansi_styles.dart';
import 'package:ci/ci.dart' as ci;
import 'package:interact/interact.dart' as interact;
import 'package:path/path.dart' show relative, normalize, windows, joinAll;

import 'platform.dart';

/// Key for windows platform.
Expand Down Expand Up @@ -153,6 +155,7 @@ String promptInput(
}

interact.SpinnerState? activeSpinnerState;

interact.SpinnerState spinner(String Function(bool) rightPrompt) {
activeSpinnerState = interact.Spinner(
icon: AnsiStyles.blue('i'),
Expand Down Expand Up @@ -211,13 +214,15 @@ String relativePath(String path, String from) {

String generateRubyScript(
String googleServiceInfoFile,
String xcodeProjFilePath,
) {
String xcodeProjFilePath, {
String? flavor,
}) {
final flavorDirectory = flavor == null ? '' : '/$flavor';
return '''
require 'xcodeproj'
googleFile='$googleServiceInfoFile'
xcodeFile='$xcodeProjFilePath'

flavor='$flavor'
# define the path to your .xcodeproj file
project_path = xcodeFile
# open the xcode project
Expand All @@ -226,23 +231,58 @@ project = Xcodeproj::Project.open(project_path)
# check if `GoogleService-Info.plist` config is set in `project.pbxproj` file.
googleConfigExists = false
project.files.each do |file|
if file.path == "Runner/GoogleService-Info.plist"
if file.path == "Runner$flavorDirectory/GoogleService-Info.plist"
googleConfigExists = true
exit
end
end

# Write only if config doesn't exist
if googleConfigExists == false
file = project.new_file(googleFile)
main_target = project.targets.find { |target| target.name == 'Runner' }

if(main_target)
main_target.add_file_references([file])
project.save
if flavor == "null"
# create a new file
file = project.new_file(googleFile)
main_target = project.targets.find { |target| target.name == 'Runner' }
if(main_target)
main_target.add_file_references([file])
project.save
else
abort("Could not find target 'Runner' in your Xcode workspace. Please rename your target to 'Runner' and try again.")
end
else
abort("Could not find target 'Runner' in your Xcode workspace. Please rename your target to 'Runner' and try again.")
end
# create a new group
currentGroup = project.new_group(flavor)
# create a new file
file = currentGroup.new_file(googleFile)
main_target = project.targets.find { |target| target.name == 'Runner' }
if(main_target)
main_target.add_file_references([file])
project.save
else
abort("Could not find target 'Runner' in your Xcode workspace. Please rename your target to 'Runner' and try again.")
end
end
end
''';
}

String buildSettingsRubyScript(String xcodeProjFilePath) {
const shellScript =
r'''"GOOGLE_SERVICE_INFO_PLIST_FROM=\"${PROJECT_DIR}/Runner/${FLAVOR}/GoogleService-Info.plist\"\nBUILD_APP_DIR=\"${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}\"\nGOOGLE_SERVICE_INFO_PLIST_TO=\"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app\"\ncp \"${GOOGLE_SERVICE_INFO_PLIST_FROM}\" \"${GOOGLE_SERVICE_INFO_PLIST_TO}\""''';
return '''
require 'xcodeproj'
xcodeFile='$xcodeProjFilePath'
project_path = xcodeFile
# open the xcode project
project = Xcodeproj::Project.open(project_path)
for target in project.targets
phase = target.new_shell_script_build_phase("google service info run phase")
phase.shell_script = $shellScript
target.build_configurations.each do |config|
flavorName = config.name.sub("Debug", "").sub("Profile", "").sub("Release", "").sub("-", "").sub("_", "").sub(" ", "")
config.build_settings['FLAVOR'] = flavorName
end
end
project.save()
''';
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:io';

import 'package:cli_util/cli_logging.dart';
import 'package:path/path.dart' as path;

import '../common/strings.dart';
import '../common/utils.dart';
import '../flutter_app.dart';
Expand Down Expand Up @@ -57,35 +58,43 @@ class FirebaseAndroidGradlePlugins {
FirebaseAndroidGradlePlugins(
this.flutterApp,
this.firebaseOptions,
this.logger,
);
this.logger, {
this.flavor,
});

final FlutterApp flutterApp;
final FirebaseOptions firebaseOptions;
final Logger logger;
final String? flavor;

File get androidGoogleServicesJsonFile => File(
path.join(
flutterApp.androidDirectory.path,
'app',
flavor != null ? 'src' : '',
flavor ?? '',
firebaseOptions.optionsSourceFileName,
),
);

File get androidBuildGradleFile =>
File(path.join(flutterApp.androidDirectory.path, 'build.gradle'));
String? _androidBuildGradleFileContents;

set androidBuildGradleFileContents(String contents) =>
_androidBuildGradleFileContents = contents;

String get androidBuildGradleFileContents =>
_androidBuildGradleFileContents ??=
androidBuildGradleFile.readAsStringSync();

File get androidAppBuildGradleFile =>
File(path.join(flutterApp.androidDirectory.path, 'app', 'build.gradle'));
String? _androidAppBuildGradleFileContents;

set androidAppBuildGradleFileContents(String contents) =>
_androidAppBuildGradleFileContents = contents;

String get androidAppBuildGradleFileContents =>
_androidAppBuildGradleFileContents ??=
androidAppBuildGradleFile.readAsStringSync();
Expand Down Expand Up @@ -120,10 +129,13 @@ class FirebaseAndroidGradlePlugins {
return;
}
}
if (!androidGoogleServicesJsonFile.parent.existsSync()) {
await androidGoogleServicesJsonFile.parent.create();
}

await androidGoogleServicesJsonFile.writeAsString(
firebaseOptions.optionsSourceContent,
);

if (!androidBuildGradleFileContents.contains(_googleServicesPluginClass)) {
final hasMatch =
_androidBuildGradleRegex.hasMatch(androidBuildGradleFileContents);
Expand Down
Loading