From ba0cf72208789d0b41bcab3355c8e6890a0df6b4 Mon Sep 17 00:00:00 2001 From: solvejet Date: Sun, 29 Mar 2026 13:52:49 +0530 Subject: [PATCH 1/2] fix: handle non-FlutterFire-generated firebase_options.dart gracefully When firebase_options.dart exists but was not generated by FlutterFire CLI (e.g. a placeholder file using UnimplementedError instead of UnsupportedError), the CLI crashes with "Exception: UnsupportedError not found in ". This fix checks whether the existing file matches the expected FlutterFire format before attempting an in-place update. If it does not match, the file is regenerated from scratch. Fixes #422 Fixes #382 --- .../firebase_dart_configuration_write.dart | 140 ++++++++---------- 1 file changed, 64 insertions(+), 76 deletions(-) diff --git a/packages/flutterfire_cli/lib/src/firebase/firebase_dart_configuration_write.dart b/packages/flutterfire_cli/lib/src/firebase/firebase_dart_configuration_write.dart index 5dd4ed04..c66c8353 100644 --- a/packages/flutterfire_cli/lib/src/firebase/firebase_dart_configuration_write.dart +++ b/packages/flutterfire_cli/lib/src/firebase/firebase_dart_configuration_write.dart @@ -53,12 +53,12 @@ class FirebaseDartConfigurationWrite { FirebaseJsonWrites write() { final outputFile = File(configurationFilePath); - if (outputFile.existsSync()) { - final updatedFileString = _updateExistingConfigurationFile( - outputFile, - ); + if (outputFile.existsSync() && _isFlutterFireGeneratedFile(outputFile)) { + final updatedFileString = _updateExistingConfigurationFile(outputFile); outputFile.writeAsStringSync(updatedFileString); } else { + // File doesn't exist or wasn't generated by FlutterFire CLI + // (e.g. a placeholder file). Generate from scratch. _writeHeader(); _writeClass(); @@ -69,9 +69,17 @@ class FirebaseDartConfigurationWrite { return _firebaseJsonWrites(); } - String _updateExistingConfigurationFile( - File outputFile, - ) { + /// Checks whether the existing file was generated by FlutterFire CLI + /// by looking for the expected structure (platform switch/if blocks). + /// Returns false for placeholder or manually written files. + bool _isFlutterFireGeneratedFile(File file) { + final contents = file.readAsStringSync(); + return contents.contains('DefaultFirebaseOptions') && + (contents.contains('kIsWeb') || + contents.contains('defaultTargetPlatform')); + } + + String _updateExistingConfigurationFile(File outputFile) { final fileConfigurationLines = outputFile.readAsLinesSync(); final optionsList = [ @@ -92,8 +100,9 @@ class FirebaseDartConfigurationWrite { if (options == null) continue; final configExists = fileConfigurationLines.any( - (line) => line - .contains('static const FirebaseOptions ${platform.toLowerCase()}'), + (line) => line.contains( + 'static const FirebaseOptions ${platform.toLowerCase()}', + ), ); if (configExists) { // find the indexes for start/end of existing platform configuration @@ -118,10 +127,7 @@ class FirebaseDartConfigurationWrite { // Insert the new platform configuration fileConfigurationLines.insertAll( startIndex - 1, - _buildFirebaseOptions( - options, - platform.toLowerCase(), - ), + _buildFirebaseOptions(options, platform.toLowerCase()), ); } else { // remove `UnsupportedError` and write static const FirebaseOptions $platform @@ -132,8 +138,9 @@ class FirebaseDartConfigurationWrite { ); final unsupportedErrorLineIndex = startIndex + 1; - if (fileConfigurationLines[unsupportedErrorLineIndex] - .contains('UnsupportedError')) { + if (fileConfigurationLines[unsupportedErrorLineIndex].contains( + 'UnsupportedError', + )) { final endIndex = fileConfigurationLines.indexWhere( (line) => line.contains(');'), unsupportedErrorLineIndex, @@ -150,7 +157,11 @@ class FirebaseDartConfigurationWrite { : ' return ${platform.toLowerCase()};', ); } else { - throw Exception('`UnsupportedError` not found in $platform'); + throw Exception( + 'Unable to update existing firebase_options.dart for platform ' + '$platform. The file exists but does not match the expected ' + 'format. Delete the file and re-run `flutterfire configure`.', + ); } final insertIndex = fileConfigurationLines.lastIndexOf('}'); @@ -158,10 +169,7 @@ class FirebaseDartConfigurationWrite { // write the static property for the platform fileConfigurationLines.insertAll( insertIndex, - _buildFirebaseOptions( - options, - platform.toLowerCase(), - ), + _buildFirebaseOptions(options, platform.toLowerCase()), ); } } @@ -179,10 +187,7 @@ class FirebaseDartConfigurationWrite { }); } - List _buildFirebaseOptions( - FirebaseOptions options, - String platform, - ) { + List _buildFirebaseOptions(FirebaseOptions options, String platform) { return [ '', ' static const FirebaseOptions $platform = FirebaseOptions(', @@ -196,10 +201,7 @@ class FirebaseDartConfigurationWrite { FirebaseJsonWrites _firebaseJsonWrites() { final relativePathConfigurationFile = replaceBackslash( - relative( - configurationFilePath, - from: flutterAppPath, - ), + relative(configurationFilePath, from: flutterAppPath), ); final keysToMap = [ @@ -238,39 +240,33 @@ class FirebaseDartConfigurationWrite { } void _writeHeader() { - _stringBuffer.writeAll( - [ - '// File generated by FlutterFire CLI.', - '// ignore_for_file: type=lint', - "import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;", - "import 'package:flutter/foundation.dart'", - ' show defaultTargetPlatform, kIsWeb, TargetPlatform;', - '', - '/// Default [FirebaseOptions] for use with your Firebase apps.', - '///', - '/// Example:', - '/// ```dart', - "/// import '${basename(configurationFilePath)}';", - '/// // ...', - '/// await Firebase.initializeApp(', - '/// options: DefaultFirebaseOptions.currentPlatform,', - '/// );', - '/// ```', - '', - ], - '\n', - ); + _stringBuffer.writeAll([ + '// File generated by FlutterFire CLI.', + '// ignore_for_file: type=lint', + "import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;", + "import 'package:flutter/foundation.dart'", + ' show defaultTargetPlatform, kIsWeb, TargetPlatform;', + '', + '/// Default [FirebaseOptions] for use with your Firebase apps.', + '///', + '/// Example:', + '/// ```dart', + "/// import '${basename(configurationFilePath)}';", + '/// // ...', + '/// await Firebase.initializeApp(', + '/// options: DefaultFirebaseOptions.currentPlatform,', + '/// );', + '/// ```', + '', + ], '\n'); } void _writeClass() { - _stringBuffer.writeAll( - [ - 'class DefaultFirebaseOptions {', - ' static FirebaseOptions get currentPlatform {', - '', - ], - '\n', - ); + _stringBuffer.writeAll([ + 'class DefaultFirebaseOptions {', + ' static FirebaseOptions get currentPlatform {', + '', + ], '\n'); _writeCurrentPlatformWeb(); _stringBuffer.writeln(' switch (defaultTargetPlatform) {'); _writeCurrentPlatformSwitchAndroid(); @@ -278,16 +274,14 @@ class FirebaseDartConfigurationWrite { _writeCurrentPlatformSwitchMacos(); _writeCurrentPlatformSwitchWindows(); _writeCurrentPlatformSwitchLinux(); - _stringBuffer.write( - ''' + _stringBuffer.write(''' default: throw UnsupportedError( 'DefaultFirebaseOptions are not supported for this platform.', ); } } -''', - ); +'''); _writeFirebaseOptionsStatic(kWeb, webOptions); _writeFirebaseOptionsStatic(kAndroid, androidOptions); _writeFirebaseOptionsStatic(kIos, iosOptions); @@ -299,23 +293,17 @@ class FirebaseDartConfigurationWrite { void _writeFirebaseOptionsStatic(String platform, FirebaseOptions? options) { if (options == null) return; - _stringBuffer.writeAll( - _buildFirebaseOptions(options, platform), - '\n', - ); + _stringBuffer.writeAll(_buildFirebaseOptions(options, platform), '\n'); } void _writeThrowUnsupportedForPlatform(String platform, String indentation) { - _stringBuffer.writeAll( - [ - '${indentation}throw UnsupportedError(', - "$indentation 'DefaultFirebaseOptions have not been configured for $platform - '", - "$indentation 'you can reconfigure this by running the FlutterFire CLI again.',", - '$indentation);', - '', - ], - '\n', - ); + _stringBuffer.writeAll([ + '${indentation}throw UnsupportedError(', + "$indentation 'DefaultFirebaseOptions have not been configured for $platform - '", + "$indentation 'you can reconfigure this by running the FlutterFire CLI again.',", + '$indentation);', + '', + ], '\n'); } void _writeCurrentPlatformWeb() { From b32853c92d0d1d57789f606baabfc59b99f6047f Mon Sep 17 00:00:00 2001 From: solvejet Date: Sun, 29 Mar 2026 14:23:15 +0530 Subject: [PATCH 2/2] refactor: use CLI header comment for file detection per review feedback --- .../lib/src/firebase/firebase_dart_configuration_write.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/flutterfire_cli/lib/src/firebase/firebase_dart_configuration_write.dart b/packages/flutterfire_cli/lib/src/firebase/firebase_dart_configuration_write.dart index c66c8353..d743095c 100644 --- a/packages/flutterfire_cli/lib/src/firebase/firebase_dart_configuration_write.dart +++ b/packages/flutterfire_cli/lib/src/firebase/firebase_dart_configuration_write.dart @@ -70,13 +70,11 @@ class FirebaseDartConfigurationWrite { } /// Checks whether the existing file was generated by FlutterFire CLI - /// by looking for the expected structure (platform switch/if blocks). + /// by looking for the CLI's header comment. /// Returns false for placeholder or manually written files. bool _isFlutterFireGeneratedFile(File file) { final contents = file.readAsStringSync(); - return contents.contains('DefaultFirebaseOptions') && - (contents.contains('kIsWeb') || - contents.contains('defaultTargetPlatform')); + return contents.startsWith('// File generated by FlutterFire CLI.'); } String _updateExistingConfigurationFile(File outputFile) {