addPermission method

OperationResult addPermission(
  1. String permissionName
)

Adds a permission to the AndroidManifest.xml file.

This method safely parses the XML, checks for duplicates, and inserts the permission in the appropriate location within the manifest.

Parameters

  • permissionName: The Android permission to add. Can be either:
    • Full permission string: android.permission.CAMERA
    • Short name: CAMERA (will be prefixed with android.permission.)

Returns

  • Success if the permission was added or already exists
  • Failure if the manifest could not be found or parsed

Example

final manager = AndroidManager('/path/to/project');

// Using full permission string
manager.addPermission('android.permission.CAMERA');

// Using short name (automatically prefixed)
manager.addPermission('INTERNET');

Manifest Changes

Before:

<manifest>
    <application>...</application>
</manifest>

After:

<manifest>
    <uses-permission android:name="android.permission.CAMERA"/>
    <application>...</application>
</manifest>

Implementation

OperationResult addPermission(String permissionName) {
  final manifestFile = File(_manifestPath);

  if (!manifestFile.existsSync()) {
    return const Failure(
      'Could not find AndroidManifest.xml. '
      'Are you in a Flutter project root directory?',
    );
  }

  // Normalize the permission name
  final fullPermission = permissionName.contains('.')
      ? permissionName
      : 'android.permission.$permissionName';

  try {
    final content = manifestFile.readAsStringSync();
    final document = XmlDocument.parse(content);

    final manifest = document.rootElement;

    if (manifest.name.local != 'manifest') {
      return const Failure(
        'Invalid AndroidManifest.xml: root element is not <manifest>.',
      );
    }

    // Check if permission already exists (prevent duplicates)
    const androidNs = 'http://schemas.android.com/apk/res/android';
    final existingPermissions = manifest.findAllElements('uses-permission');

    for (final permission in existingPermissions) {
      final nameAttr = permission.getAttribute('name', namespace: androidNs) ??
          permission.getAttribute('android:name');
      if (nameAttr == fullPermission) {
        return Success('Permission $fullPermission already exists.');
      }
    }

    // Create the new <uses-permission> element
    final permissionElement = XmlElement(
      XmlName('uses-permission'),
      [XmlAttribute(XmlName('android:name'), fullPermission)],
    );

    // Find the best insertion point
    final children = manifest.children.toList();
    int insertIndex = 0;

    // Look for existing permission/feature elements to insert after
    for (int i = 0; i < children.length; i++) {
      final child = children[i];
      if (child is XmlElement) {
        if (child.name.local == 'uses-permission' ||
            child.name.local == 'uses-feature') {
          insertIndex = i + 1;
        }
      }
    }

    // If no permission elements found, insert before <application> tag
    if (insertIndex == 0) {
      for (int i = 0; i < children.length; i++) {
        if (children[i] is XmlElement &&
            (children[i] as XmlElement).name.local == 'application') {
          insertIndex = i;
          break;
        }
      }
    }

    // Insert the permission with proper formatting
    manifest.children.insert(insertIndex, XmlText('\n    '));
    manifest.children.insert(insertIndex + 1, permissionElement);

    final output = document.toXmlString(pretty: true, indent: '    ');
    manifestFile.writeAsStringSync(output);

    return Success('Successfully added permission: $fullPermission');
  } on XmlParserException catch (e) {
    return Failure('Failed to parse AndroidManifest.xml: ${e.message}');
  } on FileSystemException catch (e) {
    return Failure('Failed to update AndroidManifest.xml: ${e.message}');
  }
}