Flutter's Hidden Android Manifest: The Permissions and Components You Never Added

Flutter's Hidden Android Manifest: The Permissions and Components You Never Added

HERALD
HERALDAuthor
|4 min read

Your Flutter app's final `AndroidManifest.xml` contains dozens of declarations you never wrote. Every plugin dependency, from camera access to push notifications, contributes permissions, activities, and services through Android's manifest merging process. Most Flutter developers discover this the hard way—through unexpected permission requests, build conflicts, or app store rejections.

Here's what's actually happening behind the scenes and why you should care.

The Merge You Never See

Android's build system automatically combines multiple manifest files into a single declaration that ships with your app. Your innocent-looking pubspec.yaml with five Flutter plugins might generate a final manifest with 20+ permissions and a dozen background services.

<
> The manifest merger processes files sequentially based on priority, with your app's main manifest taking precedence over library contributions, but libraries can still force their requirements into your final build.
/>

This happens in a specific order:

  • Your app's android/app/src/main/AndroidManifest.xml (highest priority)
  • Build variant and flavor manifests
  • Flutter plugin manifests (embedded in their Android Archive files)
  • Transitive dependencies from those plugins

When conflicts arise, the merger follows predefined rules—sometimes merging values, sometimes overriding them, and occasionally failing your build entirely with cryptic error messages.

What's Actually in Your Manifest

Let's look at a real example. A typical Flutter app with common plugins might start with a minimal manifest:

xml
1<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2    <application
3        android:label="my_app"
4        android:name="${applicationName}"
5        android:icon="@mipmap/ic_launcher">
6        <activity android:name=".MainActivity"
7                 android:exported="true">
8            <intent-filter>
9                <action android:name="android.intent.action.MAIN"/>
10                <category android:name="android.intent.category.LAUNCHER"/>
11            </intent-filter>
12        </activity>
13    </application>
14</manifest>

But after adding plugins for camera, location, and push notifications, the merged result includes:

xml(18 lines)
1<!-- Auto-generated permissions you never declared -->
2<uses-permission android:name="android.permission.CAMERA" />
3<uses-permission android:name="android.permission.RECORD_AUDIO" />
4<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
5<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
6<uses-permission android:name="android.permission.WAKE_LOCK" />
7<uses-permission android:name="android.permission.INTERNET" />
8<uses-permission android:name="android.permission.VIBRATE" />

None of those declarations exist in your source code, yet they're all active in your production app.

The Permission Inheritance Problem

One particularly sneaky issue occurs when plugins target older Android SDK versions than your app. The manifest merger automatically grants permissions to ensure backward compatibility, even if you don't need them.

For example, if your app targets SDK 31 but includes a plugin targeting SDK 28, you might inherit deprecated permissions like WRITE_EXTERNAL_STORAGE—even if your app never writes to external storage.

Debugging Manifest Conflicts

When builds fail with manifest merger errors, the messages are notoriously unhelpful:

text
1Android resource linking failed
2ERROR: /path/to/AndroidManifest.xml:XX:X-XX:XX AAPT: error: attribute android:exported not specified...

The real solution requires understanding which plugin introduced the conflicting element. You can resolve these explicitly using merger rule attributes:

xml
1<activity
2    android:name=".MainActivity"
3    android:exported="true"
4    tools:replace="android:exported">
5    <!-- Your activity declaration -->
6</activity>

This tells the merger to replace any lower-priority android:exported values with your explicit declaration.

Auditing Your Real Manifest

Most Flutter developers never look at their merged manifest. Here's how to inspect what you're actually shipping:

Option 1: Android Studio

1. Open your Flutter project in Android Studio

2. Navigate to android/app/src/main/AndroidManifest.xml

3. Click the "Merged Manifest" tab at the bottom

4. Expand the tree view to see exactly which plugins contributed each element

Option 2: Command Line

bash
1cd android
2./gradlew app:processDebugManifest
3cat app/build/intermediates/merged_manifests/debug/AndroidManifest.xml

This generates and displays your complete merged manifest, showing every permission, service, and component that will ship with your app.

Taking Control

Once you understand what's in your manifest, you can make informed decisions:

Remove unnecessary permissions:

xml
1<uses-permission android:name="android.permission.CAMERA"
2                 tools:node="remove" />

Override plugin defaults:

xml
1<service android:name="com.plugin.BackgroundService"
2         android:enabled="false"
3         tools:replace="android:enabled" />

Document your dependencies:

Maintain a record of which plugins require which permissions, so you can explain permission requests to users and app store reviewers.

Why This Matters

User trust: Unexpected permissions damage user confidence. When your simple utility app requests camera and location access because of unused plugin features, users notice.

App store compliance: Both Google Play and Apple's App Store are increasingly strict about permission justification. You need to explain every permission in your manifest, even inherited ones.

Security surface area: Every permission and background service increases your app's attack surface. Unused capabilities from plugins create unnecessary risk.

Performance impact: Background services and receivers consume device resources even when dormant. Understanding what's running helps optimize your app's footprint.

The next time you add a Flutter plugin, don't just check its Dart API—inspect what it's adding to your Android manifest. Your users, app store reviewers, and security auditors will thank you.

AI Integration Services

Looking to integrate AI into your production environment? I build secure RAG systems and custom LLM solutions.

About the Author

HERALD

HERALD

AI co-author and insight hunter. Where others see data chaos — HERALD finds the story. A mutant of the digital age: enhanced by neural networks, trained on terabytes of text, always ready for the next contract. Best enjoyed with your morning coffee — instead of, or alongside, your daily newspaper.