analyze method

  1. @override
Future<DiagnosticResult> analyze(
  1. AnalyzerContext context
)
override

Public method or function.

Implementation

@override
/// Public method or function.
Future<DiagnosticResult> analyze(AnalyzerContext context) async {
  final startTime = DateTime.now();
  final issues = <DiagnosticIssue>[];
  final fs = context.fileSystem;
  final projectPath = context.projectPath;

  final androidPath = fs.join(projectPath, 'android');
  if (!fs.exists(androidPath) || !fs.isDirectory(androidPath)) {
    return DiagnosticResult(
      analyzerName: name,
      status: CheckStatus.skipped,
      issues: issues,
      duration: DateTime.now().difference(startTime),
      timestamp: DateTime.now(),
    );
  }

  final googleServicesPath = fs.join(
    fs.join(projectPath, 'android', 'app'),
    'google-services.json',
  );

  Map<String, dynamic>? googleServicesConfig;
  if (!fs.exists(googleServicesPath)) {
    issues.add(
      DiagnosticIssue(
        severity: Severity.critical,
        code: 'FD400',
        title: 'Missing google-services.json',
        description:
            'The android/app/google-services.json file is missing. '
            'This file is required for Firebase services on Android.',
        recommendation:
            'Run "flutterfire configure" to generate the '
            'google-services.json file.',
        filePath: googleServicesPath,
      ),
    );
  } else {
    final content = fs.readAsString(googleServicesPath);
    googleServicesConfig = _parseGoogleServicesJson(content);
    if (googleServicesConfig == null) {
      issues.add(
        DiagnosticIssue(
          severity: Severity.error,
          code: 'FD401',
          title: 'Invalid google-services.json',
          description:
              'The android/app/google-services.json file contains invalid JSON.',
          recommendation:
              'Regenerate the file using "flutterfire configure" '
              'or fix the JSON syntax.',
          filePath: googleServicesPath,
        ),
      );
    }
  }

  final appDir = fs.join(projectPath, 'android', 'app');
  final appBuildGradlePath = fs.join(appDir, 'build.gradle');
  final appBuildGradleKtsPath = fs.join(appDir, 'build.gradle.kts');

  String? gradleContent;
  String? gradlePath;
  if (fs.exists(appBuildGradlePath)) {
    gradleContent = fs.readAsString(appBuildGradlePath);
    gradlePath = appBuildGradlePath;
  } else if (fs.exists(appBuildGradleKtsPath)) {
    gradleContent = fs.readAsString(appBuildGradleKtsPath);
    gradlePath = appBuildGradleKtsPath;
  }

  String? expectedPackageName;
  if (gradleContent != null) {
    final gradleInfo = _parseBuildGradle(gradleContent);

    if (!gradleInfo.plugins.contains('com.google.gms.google-services')) {
      issues.add(
        DiagnosticIssue(
          severity: Severity.error,
          code: 'FD403',
          title: 'Missing google-services plugin',
          description:
              'The com.google.gms.google-services plugin is not applied '
              'in the app-level build.gradle.',
          recommendation:
              'Add the plugin to your app-level build.gradle:\n'
              '  plugins {\n'
              '    id "com.google.gms.google-services" version "4.4.0"\n'
              '  }',
          filePath: gradlePath,
        ),
      );
    }

    if (gradleInfo.compileSdk != null && gradleInfo.compileSdk! < 34) {
      issues.add(
        DiagnosticIssue(
          severity: Severity.warning,
          code: 'FD407',
          title: 'compileSdk version is below 34',
          description:
              'The compileSdk is set to ${gradleInfo.compileSdk}. '
              'Version 34 or higher is recommended for latest Firebase SDKs.',
          recommendation:
              'Update compileSdk to 34 in your app-level build.gradle:\n'
              '  android {\n'
              '    compileSdk = 34\n'
              '  }',
          filePath: gradlePath,
        ),
      );
    }

    if (gradleInfo.minSdk != null && gradleInfo.minSdk! < 21) {
      issues.add(
        DiagnosticIssue(
          severity: Severity.info,
          code: 'FD408',
          title: 'minSdk version is below 21',
          description:
              'The minSdk is set to ${gradleInfo.minSdk}. '
              'firebase_core requires a minimum SDK version of 21.',
          recommendation:
              'Update minSdk to 21 in your app-level build.gradle:\n'
              '  defaultConfig {\n'
              '    minSdk = 21\n'
              '  }',
          filePath: gradlePath,
        ),
      );
    }

    if (gradleInfo.targetSdk != null && gradleInfo.targetSdk! < 34) {
      issues.add(
        DiagnosticIssue(
          severity: Severity.warning,
          code: 'FD409',
          title: 'targetSdk version is below 34',
          description:
              'The targetSdk is set to ${gradleInfo.targetSdk}. '
              'Version 34 or higher is recommended.',
          recommendation:
              'Update targetSdk to 34 in your app-level build.gradle:\n'
              '  defaultConfig {\n'
              '    targetSdk = 34\n'
              '  }',
          filePath: gradlePath,
        ),
      );
    }

    expectedPackageName = gradleInfo.applicationId;
  }

  if (googleServicesConfig != null &&
      googleServicesConfig['package_name'] != null &&
      expectedPackageName != null) {
    final gsPackageName = googleServicesConfig['package_name'] as String;
    if (gsPackageName != expectedPackageName) {
      issues.add(
        DiagnosticIssue(
          severity: Severity.error,
          code: 'FD402',
          title: 'Package name mismatch in google-services.json',
          description:
              'The package name "$gsPackageName" in google-services.json '
              'does not match the applicationId "$expectedPackageName" '
              'in build.gradle.',
          recommendation:
              'Update the applicationId in build.gradle or regenerate '
              'google-services.json with "flutterfire configure".',
          filePath: googleServicesPath,
        ),
      );
    }
  }

  final manifestPath = fs.join(
    fs.join(fs.join(projectPath, 'android', 'app'), 'src', 'main'),
    'AndroidManifest.xml',
  );

  if (fs.exists(manifestPath)) {
    final manifestContent = fs.readAsString(manifestPath);
    final manifestInfo = _parseAndroidManifestXml(manifestContent);

    if (!manifestInfo.permissions.contains('android.permission.INTERNET')) {
      issues.add(
        DiagnosticIssue(
          severity: Severity.error,
          code: 'FD404',
          title: 'Missing INTERNET permission',
          description:
              'The INTERNET permission is not declared in '
              'AndroidManifest.xml.',
          recommendation:
              'Add the INTERNET permission to your AndroidManifest.xml:\n'
              '  <uses-permission android:name="android.permission.INTERNET"/>',
          filePath: manifestPath,
        ),
      );
    }

    if (!manifestInfo.permissions.contains(
      'android.permission.POST_NOTIFICATIONS',
    )) {
      issues.add(
        DiagnosticIssue(
          severity: Severity.warning,
          code: 'FD405',
          title: 'Missing POST_NOTIFICATIONS permission',
          description:
              'The POST_NOTIFICATIONS permission is not declared. '
              'Required for Android 13+ (API level 33+) notification support.',
          recommendation:
              'Add the POST_NOTIFICATIONS permission to '
              'your AndroidManifest.xml:\n'
              '  <uses-permission '
              'android:name="android.permission.POST_NOTIFICATIONS"/>',
          filePath: manifestPath,
        ),
      );
    }

    if (!manifestInfo.permissions.contains('android.permission.WAKE_LOCK')) {
      issues.add(
        DiagnosticIssue(
          severity: Severity.info,
          code: 'FD406',
          title: 'Missing WAKE_LOCK permission',
          description:
              'The WAKE_LOCK permission is not declared in '
              'AndroidManifest.xml.',
          recommendation:
              'Add the WAKE_LOCK permission to your AndroidManifest.xml:\n'
              '  <uses-permission android:name="android.permission.WAKE_LOCK"/>',
          filePath: manifestPath,
        ),
      );
    }
  }

  final hasCriticalOrError = issues.any(
    (i) => i.severity == Severity.critical || i.severity == Severity.error,
  );
  final hasWarning = issues.any((i) => i.severity == Severity.warning);

  CheckStatus status;
  if (hasCriticalOrError) {
    status = CheckStatus.failed;
  } else if (hasWarning) {
    status = CheckStatus.warning;
  } else {
    status = CheckStatus.passed;
  }

  return DiagnosticResult(
    analyzerName: name,
    status: status,
    issues: issues,
    duration: DateTime.now().difference(startTime),
    timestamp: DateTime.now(),
  );
}