From 4813376ae36da1a60583282c1dde863616515f8a Mon Sep 17 00:00:00 2001
From: Frederik St-Onge <frederik.stonge@gmail.com>
Date: Mon, 6 Jan 2025 13:42:00 -0500
Subject: [PATCH] Use versions from firebase_core pubspec

---
 .../src/firebase/firebase_android_writes.dart | 78 +++++++++++++++----
 .../src/firebase/firebase_pubspec_model.dart  | 19 +++++
 2 files changed, 82 insertions(+), 15 deletions(-)
 create mode 100644 packages/flutterfire_cli/lib/src/firebase/firebase_pubspec_model.dart

diff --git a/packages/flutterfire_cli/lib/src/firebase/firebase_android_writes.dart b/packages/flutterfire_cli/lib/src/firebase/firebase_android_writes.dart
index 9fe60a65..452048a3 100644
--- a/packages/flutterfire_cli/lib/src/firebase/firebase_android_writes.dart
+++ b/packages/flutterfire_cli/lib/src/firebase/firebase_android_writes.dart
@@ -1,11 +1,15 @@
 import 'dart:io';
 
 import 'package:cli_util/cli_logging.dart';
+import 'package:collection/collection.dart';
 import 'package:path/path.dart' as path;
+import 'package:pubspec/pubspec.dart';
+import 'package:yaml/yaml.dart';
 import '../common/strings.dart';
 import '../common/utils.dart';
 import '../flutter_app.dart';
 import 'firebase_options.dart';
+import 'firebase_pubspec_model.dart';
 
 // https://regex101.com/r/Lj93lx/1
 final _androidBuildGradleRegex = RegExp(
@@ -31,22 +35,13 @@ final _androidAppBuildGradleGoogleServicesRegex = RegExp(
 // Google services JSON.
 const _googleServicesPluginClass = 'com.google.gms:google-services';
 const _googleServicesPluginName = 'com.google.gms.google-services';
-// TODO read from firebase_core pubspec.yaml firebase.google_services_gradle_plugin_version
-const _googleServicesPluginVersion = '4.3.15';
-const _googleServicesPlugin =
-    "classpath '$_googleServicesPluginClass:$_googleServicesPluginVersion'";
 
 // Firebase Crashlytics
-const _crashlyticsPluginClassPath =
-    'com.google.firebase:firebase-crashlytics-gradle';
-// TODO read from firebase_core pubspec.yaml firebase.crashlytics_gradle_plugin_version
-const _crashlyticsPluginClassPathVersion = '2.8.1';
+const _crashlyticsPluginClassPath = 'com.google.firebase:firebase-crashlytics-gradle';
 const _crashlyticsPluginClass = 'com.google.firebase.crashlytics';
 
 // Firebase Performance
 const _performancePluginClassPath = 'com.google.firebase:perf-plugin';
-// TODO read from firebase_core pubspec.yaml firebase.performance_gradle_plugin_version
-const _performancePluginClassPathVersion = '1.4.1';
 const _performancePluginClass = 'com.google.firebase.firebase-perf';
 
 const _flutterFireConfigCommentStart = '// START: FlutterFire Configuration';
@@ -205,6 +200,8 @@ Future<void> gradleContentUpdates(
     ),
   );
 
+  final firebaseCorePubSpec = await getFirebaseCorePubSpec();
+
   final androidGradleSettingsFileContents =
       androidGradleSettingsFile.readAsStringSync();
 
@@ -224,16 +221,19 @@ Future<void> gradleContentUpdates(
       flutterApp,
       content,
       BuildGradleConfiguration.legacy1,
+      firebaseCorePubSpec.googleServicesGradlePluginVersion,
     );
     content = _applyCrashlyticsPlugin(
       flutterApp,
       content,
       BuildGradleConfiguration.legacy1,
+      firebaseCorePubSpec.crashlyticsGradlePluginVersion,
     );
     content = _applyPerformancePlugin(
       flutterApp,
       content,
       BuildGradleConfiguration.legacy1,
+      firebaseCorePubSpec.performanceGradlePluginVersion,
     );
   }
 
@@ -250,16 +250,19 @@ Future<void> gradleContentUpdates(
       flutterApp,
       content,
       BuildGradleConfiguration.legacy2,
+      firebaseCorePubSpec.googleServicesGradlePluginVersion,
     );
     content = _applyCrashlyticsPlugin(
       flutterApp,
       content,
       BuildGradleConfiguration.legacy2,
+      firebaseCorePubSpec.crashlyticsGradlePluginVersion,
     );
     content = _applyPerformancePlugin(
       flutterApp,
       content,
       BuildGradleConfiguration.legacy2,
+      firebaseCorePubSpec.performanceGradlePluginVersion,
     );
   }
 
@@ -274,16 +277,19 @@ Future<void> gradleContentUpdates(
       flutterApp,
       content,
       BuildGradleConfiguration.latest,
+      firebaseCorePubSpec.googleServicesGradlePluginVersion,
     );
     content = _applyCrashlyticsPlugin(
       flutterApp,
       content,
       BuildGradleConfiguration.latest,
+      firebaseCorePubSpec.crashlyticsGradlePluginVersion,
     );
     content = _applyPerformancePlugin(
       flutterApp,
       content,
       BuildGradleConfiguration.latest,
+      firebaseCorePubSpec.performanceGradlePluginVersion,
     );
 
     // WRITE <app>/android/settings.gradle. We only need to do this with latest Flutter version >= 3.16.5
@@ -303,6 +309,7 @@ Future<void> gradleContentUpdates(
 
 AndroidGradleContents _legacyUpdateAndroidBuildGradle(
   AndroidGradleContents content,
+  String googleServicesGradlePluginVersion,
 ) {
   var androidBuildGradleFileContents = content.buildGradleContent;
 
@@ -327,7 +334,9 @@ AndroidGradleContents _legacyUpdateAndroidBuildGradle(
   androidBuildGradleFileContents = androidBuildGradleFileContents
       .replaceFirstMapped(_androidBuildGradleRegex, (match) {
     const indentation = '        ';
-    return '${match.group(0)}\n$indentation$_flutterFireConfigCommentStart\n$indentation$_googleServicesPlugin\n$indentation$_flutterFireConfigCommentEnd';
+    final googleServicesPlugin =
+        "classpath '$_googleServicesPluginClass:$googleServicesGradlePluginVersion'";
+    return '${match.group(0)}\n$indentation$_flutterFireConfigCommentStart\n$indentation$googleServicesPlugin\n$indentation$_flutterFireConfigCommentEnd';
   });
 
   return AndroidGradleContents(
@@ -341,6 +350,7 @@ AndroidGradleContents _applyGoogleServicesPlugin(
   FlutterApp flutterApp,
   AndroidGradleContents content,
   BuildGradleConfiguration buildGradleConfiguration,
+  String googleServicesGradlePluginVersion,
 ) {
   var androidBuildGradleFileContents = content.buildGradleContent;
   var androidAppBuildGradleFileContents = content.appBuildGradleContent;
@@ -348,7 +358,7 @@ AndroidGradleContents _applyGoogleServicesPlugin(
 
   if (buildGradleConfiguration == BuildGradleConfiguration.legacy1 ||
       buildGradleConfiguration == BuildGradleConfiguration.legacy2) {
-    final updatedContent = _legacyUpdateAndroidBuildGradle(content);
+    final updatedContent = _legacyUpdateAndroidBuildGradle(content, googleServicesGradlePluginVersion);
     androidBuildGradleFileContents = updatedContent.buildGradleContent;
   }
 
@@ -408,7 +418,7 @@ AndroidGradleContents _applyGoogleServicesPlugin(
         final endIndex = match.end;
         final toInsert = _applyGradleSettingsDependency(
           _googleServicesPluginName,
-          _googleServicesPluginVersion,
+          googleServicesGradlePluginVersion,
           flutterfireComments: true,
         );
 
@@ -431,13 +441,14 @@ AndroidGradleContents _applyCrashlyticsPlugin(
   FlutterApp flutterApp,
   AndroidGradleContents content,
   BuildGradleConfiguration buildGradleConfiguration,
+  String crashlyticsGradlePluginVersion,
 ) {
   // do not apply if firebase_crashlytics is not present
   if (!flutterApp.dependsOnPackage('firebase_crashlytics')) return content;
 
   return _applyFirebaseAndroidPlugin(
     pluginClassPath: _crashlyticsPluginClassPath,
-    pluginClassPathVersion: _crashlyticsPluginClassPathVersion,
+    pluginClassPathVersion: crashlyticsGradlePluginVersion,
     pluginClass: _crashlyticsPluginClass,
     content: content,
     buildGradleConfiguration: buildGradleConfiguration,
@@ -448,13 +459,14 @@ AndroidGradleContents _applyPerformancePlugin(
   FlutterApp flutterApp,
   AndroidGradleContents content,
   BuildGradleConfiguration buildGradleConfiguration,
+  String performanceGradlePluginVersion,
 ) {
   // do not apply if firebase_performance is not present
   if (!flutterApp.dependsOnPackage('firebase_performance')) return content;
 
   return _applyFirebaseAndroidPlugin(
     pluginClassPath: _performancePluginClassPath,
-    pluginClassPathVersion: _performancePluginClassPathVersion,
+    pluginClassPathVersion: performanceGradlePluginVersion,
     pluginClass: _performancePluginClass,
     content: content,
     buildGradleConfiguration: buildGradleConfiguration,
@@ -576,3 +588,39 @@ AndroidGradleContents _applyFirebaseAndroidPlugin({
     gradleSettingsContent: androidGradleSettingsFileContents,
   );
 }
+
+Future<FirebasePubSpecModel> getFirebaseCorePubSpec() async {
+  final pubCacheFolder = _getPubCacheDirectory();
+  final items = pubCacheFolder.listSync();
+  final firebaseCoreDirectory = items
+      .whereType<Directory>()
+      .where((e) => e.path.split('/').last.startsWith('firebase_core-'))
+      .sortedBy((d) => d.path)
+      .last;
+
+  final firebaseCorePubspecFile =
+      pubspecPathForDirectory(firebaseCoreDirectory);
+  final pubSpec = await PubSpec.loadFile(firebaseCorePubspecFile);
+  final unparsedJson = pubSpec.unParsedYaml?['firebase'] as YamlMap?;
+  if (unparsedJson == null) {
+    throw Exception('Could not parse firebase_core pubspec.yaml');
+  }
+
+  return FirebasePubSpecModel.fromJson(unparsedJson.cast<String, dynamic>());
+}
+
+Directory _getPubCacheDirectory() {
+  final env = Platform.environment;
+
+  if (env.containsKey('PUB_CACHE')) {
+    return Directory(env['PUB_CACHE']!);
+  } else if (Platform.isWindows) {
+    return Directory(
+      path.join(env['LOCALAPPDATA']!, 'Pub', 'Cache', 'hosted', 'pub.dev'),
+    );
+  } else {
+    return Directory(
+      path.join('${env['HOME']}/.pub-cache', 'hosted', 'pub.dev'),
+    );
+  }
+}
diff --git a/packages/flutterfire_cli/lib/src/firebase/firebase_pubspec_model.dart b/packages/flutterfire_cli/lib/src/firebase/firebase_pubspec_model.dart
new file mode 100644
index 00000000..2c6c62cb
--- /dev/null
+++ b/packages/flutterfire_cli/lib/src/firebase/firebase_pubspec_model.dart
@@ -0,0 +1,19 @@
+class FirebasePubSpecModel {
+  const FirebasePubSpecModel({
+    required this.googleServicesGradlePluginVersion,
+    required this.crashlyticsGradlePluginVersion,
+    required this.performanceGradlePluginVersion,
+  });
+
+  factory FirebasePubSpecModel.fromJson(Map<String, dynamic> json) {
+    return FirebasePubSpecModel(
+      googleServicesGradlePluginVersion: json['google_services_gradle_plugin_version'],
+      crashlyticsGradlePluginVersion: json['crashlytics_gradle_plugin_version'],
+      performanceGradlePluginVersion: json['performance_gradle_plugin_version'],
+    );
+  }
+
+  final String googleServicesGradlePluginVersion;
+  final String crashlyticsGradlePluginVersion;
+  final String performanceGradlePluginVersion;
+}
\ No newline at end of file