generateAndroidManifestFromSpec function
Renders the host AndroidManifest from a typed ManifestSpec (ADR-0006).
Implementation
String generateAndroidManifestFromSpec({
required String packageName,
required String label,
required String minSdk,
required String targetSdk,
String activityName = '.MainActivity',
ManifestSpec spec = const ManifestSpec(),
String extraIntentFilters = '',
String iconRef = '',
}) {
final permLines = spec.permissions
.map((p) => ' <uses-permission android:name="$p" />')
.join('\n');
// Application attributes: typed spec fields first, then passthrough map.
final appAttrs = <String, String>{
'android:label': label,
if (iconRef.isNotEmpty) 'android:icon': iconRef,
'android:debuggable': '${spec.debuggable}',
'android:extractNativeLibs': '${spec.extractNativeLibs}',
if (spec.cleartextTraffic != null)
'android:usesCleartextTraffic': '${spec.cleartextTraffic}',
...spec.applicationAttributes,
};
final appAttrLines = appAttrs.entries
.map((e) => ' ${e.key}="${e.value}"')
.join('\n');
final metaData = <MetaDataSpec>[
...spec.applicationMetaData,
if (spec.flutterDeeplinking)
const MetaDataSpec(name: 'flutter_deeplinking_enabled', value: 'true'),
];
final metaDataLines = metaData
.map(
(m) => m.resource != null
? ' <meta-data android:name="${m.name}" '
'android:resource="${m.resource}" />'
: ' <meta-data android:name="${m.name}" '
'android:value="${m.value}" />',
)
.join('\n');
// Raw application-level elements (services, receivers, providers, ...).
final appElements = spec.applicationElements
.map((e) => ' $e')
.join('\n');
final actAttrLines = spec.activityAttributes.entries
.map((e) => ' ${e.key}="${e.value}"')
.join('\n');
// Activity-level meta-data; a spec-provided NormalTheme replaces the
// hardcoded `@android:style` fallback (project launch themes).
final hasNormalTheme = spec.activityMetaData.any(
(m) => m.name == 'io.flutter.embedding.android.NormalTheme',
);
final actMetaDataLines = spec.activityMetaData
.map(
(m) => m.resource != null
? ' <meta-data android:name="${m.name}" '
'android:resource="${m.resource}" />'
: ' <meta-data android:name="${m.name}" '
'android:value="${m.value}" />',
)
.join('\n');
// Hardcoded embedding fallback only when the spec does not define its own
// NormalTheme (project launch themes, e.g. @style/NormalTheme).
final normalThemeMetaData = hasNormalTheme
? ''
: '''
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@android:style/Theme.Light.NoTitleBar" />''';
// Raw manifest-level elements (queries, uses-feature, ...).
final rawElements = spec.manifestElements.join('\n');
final filters = <String>[
if (spec.deeplinks.isNotEmpty) renderDeeplinkIntentFilters(spec.deeplinks),
if (extraIntentFilters.isNotEmpty) extraIntentFilters,
].join('\n');
return '''
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="$packageName">
<uses-sdk
android:minSdkVersion="$minSdk"
android:targetSdkVersion="$targetSdk" />
$permLines
$rawElements
<application
$appAttrLines>
<activity
android:name="$activityName"
android:exported="true"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Light.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
$actAttrLines
$actMetaDataLines$normalThemeMetaData
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
$filters
</activity>
$metaDataLines
$appElements
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
''';
}