Flutter Native Integration & Platform Configuration Files
Answer
Overview
A Flutter project spans two native platforms — Android and iOS — each with its own set of configuration files. Understanding what each file does and when to modify it is essential for native integration, permissions, plugin setup, and release builds.
textflutter_project/ ├── pubspec.yaml # Flutter dependencies & assets ├── lib/ │ └── main.dart # Flutter entry point ├── android/ │ ├── app/ │ │ ├── src/main/ │ │ │ ├── AndroidManifest.xml # App permissions & config │ │ │ └── kotlin/com/example/app/ │ │ │ ├── MainActivity.kt # Android entry activity │ │ │ └── MainApplication.kt # Android application class │ │ ├── build.gradle.kts # App-level build config │ │ └── proguard-rules.pro # Code shrinking rules │ ├── settings.gradle.kts # Project-level Gradle settings │ ├── gradle.properties # Gradle JVM & project flags │ └── local.properties # Local SDK path (not committed) └── ios/ ├── Runner/ │ ├── Info.plist # iOS app permissions & metadata │ ├── AppDelegate.swift # iOS app lifecycle entry │ └── Runner.entitlements # iOS capabilities ├── Podfile # iOS CocoaPods dependencies ├── Runner.xcworkspace # ← Always open this in Xcode └── Runner.xcodeproj # Xcode project structure
Flutter Configuration
1. pubspec.yaml
The central configuration file for every Flutter project. Defines dependencies, assets, fonts, and project metadata.
yamlname: my_flutter_app description: A Flutter application version: 1.0.0+1 # version_name+version_code environment: sdk: '>=3.0.0 <4.0.0' # Dart SDK constraint dependencies: flutter: sdk: flutter # Third-party packages dio: ^5.3.0 riverpod: ^2.4.0 cached_network_image: ^3.3.0 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.0 build_runner: ^2.4.0 flutter: uses-material-design: true # Asset declarations — must declare every asset file/folder assets: - assets/images/ - assets/icons/logo.png - assets/data/config.json # Custom fonts fonts: - family: Poppins fonts: - asset: assets/fonts/Poppins-Regular.ttf - asset: assets/fonts/Poppins-Bold.ttf weight: 700
When to modify: Adding packages, declaring assets/fonts, changing app version.
2. main.dart
The Flutter entry point — the first Dart file executed when the app launches.
dartimport 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; Future<void> main() async { // Must be called before any Flutter framework usage WidgetsFlutterBinding.ensureInitialized(); // Initialize plugins that need async setup await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); // Set preferred device orientations await SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, ]); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); Widget build(BuildContext context) { return MaterialApp( title: 'My App', theme: ThemeData(colorSchemeSeed: Colors.blue), home: const HomeScreen(), ); } }
When to modify: Plugin initialization, app-level configuration, orientation locking, theme setup.
Android Configuration Files
3. AndroidManifest.xml
Located at
android/app/src/main/AndroidManifest.xmlxml<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Permissions --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <application android:label="My App" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:usesCleartextTraffic="false" android:enableOnBackInvokedCallback="true"> <!-- Main Activity --> <activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:windowSoftInputMode="adjustResize"> <meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <!-- Deep link intent filter --> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="https" android:host="myapp.com" /> </intent-filter> </activity> <!-- Firebase / push notifications --> <meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="default_channel" /> </application> </manifest>
When to modify: Adding permissions, deep links, push notification config, plugin registration.
4. MainActivity.kt
Located at
android/app/src/main/kotlin/.../MainActivity.ktkotlinpackage com.example.myapp import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class MainActivity : FlutterActivity() { private val CHANNEL = "com.example.myapp/battery" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) // Register a Platform Channel MethodChannel( flutterEngine.dartExecutor.binaryMessenger, CHANNEL ).setMethodCallHandler { call, result -> when (call.method) { "getBatteryLevel" -> { val level = getBatteryLevel() if (level != -1) result.success(level) else result.error("UNAVAILABLE", "Battery level not available", null) } else -> result.notImplemented() } } } private fun getBatteryLevel(): Int { val batteryManager = getSystemService(BATTERY_SERVICE) as android.os.BatteryManager return batteryManager.getIntProperty(android.os.BatteryManager.BATTERY_PROPERTY_CAPACITY) } }
When to modify: Registering platform channels, handling Android-specific intents, overriding activity lifecycle.
5. MainApplication.kt
Located alongside
MainActivity.ktkotlinpackage com.example.myapp import io.flutter.app.FlutterApplication // For older Flutter projects — newer ones use ${applicationName} in manifest class MainApplication : FlutterApplication() { override fun onCreate() { super.onCreate() // App-wide initializations that must run before any Activity // e.g. crash reporting, dependency injection } }
Modern Flutter projects use
${applicationName}AndroidManifest.xmlMainApplicationWhen to modify: App-wide Android initialization (crash reporters, DI frameworks, WorkManager).
6. android/app/build.gradle.kts
The app-level build script — controls SDK versions, app ID, versioning, signing, and dependencies.
kotlinplugins { id("com.android.application") id("kotlin-android") id("dev.flutter.flutter-gradle-plugin") id("com.google.gms.google-services") // Firebase } android { namespace = "com.example.myapp" compileSdk = flutter.compileSdkVersion defaultConfig { applicationId = "com.example.myapp" minSdk = 21 // Minimum Android version (Android 5.0) targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName multiDexEnabled = true // Required for large apps } // Release signing configuration signingConfigs { create("release") { keyAlias = System.getenv("KEY_ALIAS") keyPassword = System.getenv("KEY_PASSWORD") storeFile = file(System.getenv("KEYSTORE_PATH") ?: "keystore.jks") storePassword = System.getenv("STORE_PASSWORD") } } buildTypes { release { signingConfig = signingConfigs.getByName("release") isMinifyEnabled = true // Enable code shrinking isShrinkResources = true // Remove unused resources proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } debug { isDebuggable = true } } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = "17" } } flutter { source = "../.." } dependencies { implementation(platform("com.google.firebase:firebase-bom:32.7.0")) implementation("com.google.firebase:firebase-analytics") implementation("androidx.multidex:multidex:2.0.1") }
When to modify: Changing minSdk, adding signing config, enabling multidex, adding native Android dependencies.
7. settings.gradle.kts
The project-level Gradle settings — defines which modules are included and configures plugin repositories.
kotlinpluginManagement { val flutterSdkPath = run { val properties = java.util.Properties() file("local.properties").inputStream().use { properties.load(it) } val flutterSdkPath = properties.getProperty("flutter.sdk") require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } flutterSdkPath } includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { google() mavenCentral() gradlePluginPortal() } } plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.3.0" apply false id("org.jetbrains.kotlin.android") version "1.9.22" apply false id("com.google.gms.google-services") version "4.4.1" apply false } include(":app")
When to modify: Adding Gradle plugins (Firebase, Crashlytics), changing Kotlin/AGP versions.
8. gradle.properties
Project-wide Gradle configuration flags — controls JVM memory, AndroidX, and build optimizations.
properties# JVM heap size — increase if Gradle runs out of memory org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError # Enable Gradle daemon for faster builds org.gradle.daemon=true # Enable parallel project execution org.gradle.parallel=true # Enable Gradle build cache org.gradle.caching=true # AndroidX migration (mandatory for modern Flutter) android.useAndroidX=true android.enableJetifier=true # R8 full mode for better shrinking android.enableR8.fullMode=false # Kotlin code style kotlin.code.style=official
When to modify: Increasing JVM memory (build OOM errors), enabling AndroidX, tuning build performance.
9. local.properties
Machine-specific configuration — stores local SDK paths. Never commit to Git (already in
.gitignoreproperties# Android SDK location on this machine sdk.dir=/Users/venkatraman/Library/Android/sdk # Flutter SDK location flutter.sdk=/Users/venkatraman/fvm/versions/3.19.0 # App version (read by build.gradle.kts via flutter.versionName/Code) flutter.versionName=1.0.0 flutter.versionCode=1 flutter.buildMode=release
When to modify: Typically auto-generated. Modify manually if SDK path changes or using FVM.
10. proguard-rules.pro
Android code shrinking rules — tells R8/ProGuard what NOT to remove when
isMinifyEnabled = truepro# Keep Flutter engine classes -keep class io.flutter.** { *; } -keep class io.flutter.embedding.** { *; } # Keep plugin classes (add rules for each plugin you use) -keep class com.google.firebase.** { *; } -keep class com.google.android.gms.** { *; } # Keep model classes used with Gson/JSON serialization -keep class com.example.myapp.models.** { *; } -keepclassmembers class com.example.myapp.models.** { *; } # Keep Kotlin Serialization -keepattributes *Annotation* -keepclassmembers class kotlinx.serialization.json.** { *** *; } # Prevent obfuscation of class names used with reflection -keepnames class * implements java.io.Serializable # Keep enums -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # Suppress warnings for missing classes (optional) -dontwarn org.bouncycastle.** -dontwarn com.sun.jna.**
When to modify: When release builds crash with
ClassNotFoundException-keepiOS Configuration Files
11. Info.plist
Located at
ios/Runner/Info.plistxml<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ...> <plist version="1.0"> <dict> <!-- App display name --> <key>CFBundleDisplayName</key> <string>My App</string> <!-- Bundle ID --> <key>CFBundleIdentifier</key> <string>com.example.myapp</string> <!-- Version --> <key>CFBundleShortVersionString</key> <string>$(FLUTTER_BUILD_NAME)</string> <key>CFBundleVersion</key> <string>$(FLUTTER_BUILD_NUMBER)</string> <!-- Privacy usage descriptions — required for App Store --> <key>NSCameraUsageDescription</key> <string>This app needs camera access to take photos.</string> <key>NSPhotoLibraryUsageDescription</key> <string>This app needs photo library access to select images.</string> <key>NSLocationWhenInUseUsageDescription</key> <string>This app uses your location to show nearby places.</string> <key>NSMicrophoneUsageDescription</key> <string>This app uses the microphone for voice recording.</string> <key>NSFaceIDUsageDescription</key> <string>This app uses Face ID for secure authentication.</string> <!-- Deep link URL scheme --> <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>myapp</string> </array> </dict> </array> <!-- Allow HTTP for specific domains (disable ATS) --> <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <false/> <key>NSExceptionDomains</key> <dict> <key>api.myapp.com</key> <dict> <key>NSExceptionAllowsInsecureHTTPLoads</key> <true/> </dict> </dict> </dict> <!-- Background modes --> <key>UIBackgroundModes</key> <array> <string>fetch</string> <string>remote-notification</string> </array> </dict> </plist>
When to modify: Adding permissions, URL schemes for deep links, background modes, App Transport Security exceptions.
12. AppDelegate.swift
Located at
ios/Runner/AppDelegate.swiftswiftimport UIKit import Flutter import Firebase @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { // Configure Firebase FirebaseApp.configure() // Register Flutter plugins (auto-registered in modern Flutter) GeneratedPluginRegistrant.register(with: self) // Register a Platform Channel let controller = window?.rootViewController as! FlutterViewController let batteryChannel = FlutterMethodChannel( name: "com.example.myapp/battery", binaryMessenger: controller.binaryMessenger ) batteryChannel.setMethodCallHandler { call, result in guard call.method == "getBatteryLevel" else { result(FlutterMethodNotImplemented) return } self.receiveBatteryLevel(result: result) } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } // Handle push notification registration override func application( _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { Messaging.messaging().apnsToken = deviceToken super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) } // Handle deep links override func application( _ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] ) -> Bool { return super.application(app, open: url, options: options) } private func receiveBatteryLevel(result: FlutterResult) { UIDevice.current.isBatteryMonitoringEnabled = true if UIDevice.current.batteryState == .unknown { result(FlutterError(code: "UNAVAILABLE", message: "Battery info unavailable", details: nil)) } else { result(Int(UIDevice.current.batteryLevel * 100)) } } }
When to modify: Firebase setup, platform channels, push notifications, deep link handling, iOS lifecycle hooks.
13. Runner.entitlements
Located at
ios/Runner/Runner.entitlementsxml<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ...> <plist version="1.0"> <dict> <!-- Push Notifications (APNs) --> <key>aps-environment</key> <string>development</string> <!-- Use 'production' for App Store --> <!-- iCloud --> <key>com.apple.developer.icloud-container-identifiers</key> <array> <string>iCloud.com.example.myapp</string> </array> <!-- Sign in with Apple --> <key>com.apple.developer.applesignin</key> <array> <string>Default</string> </array> <!-- HealthKit --> <key>com.apple.developer.healthkit</key> <true/> <!-- Associated Domains (Universal Links / deep links) --> <key>com.apple.developer.associated-domains</key> <array> <string>applinks:myapp.com</string> <string>webcredentials:myapp.com</string> </array> </dict> </plist>
When to modify: Enabling push notifications, Universal Links, Sign in with Apple, iCloud, HealthKit, or any capability that needs Apple's entitlement system.
14. Podfile
Located at
ios/Podfileruby# Minimum iOS version — must match flutter plugin requirements platform :ios, '13.0' # CocoaPods analytics opt-out ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path( File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__ ) File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) # Additional native pods (if needed directly) # pod 'GoogleMaps', '~> 8.0' target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) # Fix deployment target warnings target.build_configurations.each do |config| config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' end end end
Commands:
bashcd ios && pod install # Install/update pods cd ios && pod update # Update all pods to latest cd ios && pod deintegrate && pod install # Clean reinstall
When to modify: Changing minimum iOS version, fixing pod conflicts, adding native iOS-only libraries.
15. Runner.xcworkspace
Located at
ios/Runner.xcworkspaceAlways open
, nevertextRunner.xcworkspacedirectly. OpeningtextRunner.xcodeprojalone misses CocoaPods dependencies and will fail to build.text.xcodeproj
bash# Open correct file open ios/Runner.xcworkspace # Or via Flutter flutter build ios # Build for device flutter run --release # Run release on connected device
Contains:
- — the actual Xcode projecttext
Runner.xcodeproj - — auto-generated by CocoaPodstext
Pods.xcodeproj
When to modify: Indirectly — through Xcode for signing, capabilities, build settings.
16. Runner.xcodeproj
Located at
ios/Runner.xcodeprojKey files inside:
| File | Purpose |
|---|---|
text | Machine-generated project structure (avoid manual edits) |
text | Build schemes (Debug, Release, Profile) |
Common Xcode settings configured here:
- Bundle Identifier
- Signing Team & Provisioning Profile
- Build Number / Version
- Deployment Target
- Capabilities (automatically mirrors )text
Runner.entitlements
When to modify: Via Xcode UI — for signing, capabilities, adding files to the build target.
17. Platform Channels
The mechanism for Flutter ↔ Native communication. Three channel types:
| Channel Type | Direction | Use Case |
|---|---|---|
text | Bidirectional (request-response) | Single calls (battery level, file picker) |
text | Native → Flutter (stream) | Continuous data (sensors, connectivity) |
text | Bidirectional (messages) | Custom codec communication |
MethodChannel Example
Flutter side (Dart):
dartimport 'package:flutter/services.dart'; class BatteryService { static const _channel = MethodChannel('com.example.myapp/battery'); static Future<int> getBatteryLevel() async { try { final int level = await _channel.invokeMethod('getBatteryLevel'); return level; } on PlatformException catch (e) { throw Exception('Failed to get battery level: ${e.message}'); } } } // Usage in widget final level = await BatteryService.getBatteryLevel();
Android side (Kotlin) — in MainActivity.kt:
kotlinMethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.myapp/battery") .setMethodCallHandler { call, result -> if (call.method == "getBatteryLevel") { result.success(getBatteryLevel()) } else { result.notImplemented() } }
iOS side (Swift) — in AppDelegate.swift:
swiftFlutterMethodChannel(name: "com.example.myapp/battery", binaryMessenger: controller.binaryMessenger) .setMethodCallHandler { call, result in if call.method == "getBatteryLevel" { result(Int(UIDevice.current.batteryLevel * 100)) } else { result(FlutterMethodNotImplemented) } }
EventChannel Example — Continuous Stream
dart// Flutter — listen to native stream const _channel = EventChannel('com.example.myapp/connectivity'); Stream<bool> get connectivityStream => _channel.receiveBroadcastStream().map((event) => event as bool);
kotlin// Android — send events to Flutter EventChannel(messenger, "com.example.myapp/connectivity") .setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventChannel.EventSink) { // Send connectivity updates events.success(isConnected) } override fun onCancel(arguments: Any?) {} })
Quick Reference Table
| File | Platform | Purpose | Modify When |
|---|---|---|---|
text | Flutter | Dependencies, assets, fonts | Adding packages or assets |
text | Flutter | App entry point | Plugin init, theme, orientation |
text | Android | Permissions, activities | Adding permissions, deep links |
text | Android | Flutter activity + channels | Platform channels, intents |
text | Android | App-level Android init | App-wide native initialization |
text | Android | SDK versions, signing, deps | minSdk, signing, native deps |
text | Android | Gradle modules & plugins | Adding Gradle plugins |
text | Android | JVM memory, AndroidX flags | OOM errors, build tuning |
text | Android | Local SDK paths | SDK path changes, FVM |
text | Android | Code shrinking rules | Release crashes, R8 issues |
text | iOS | Permissions, URL schemes | Adding permissions, deep links |
text | iOS | iOS lifecycle + channels | Firebase, push, channels |
text | iOS | iOS capabilities | Push notif, Universal Links |
text | iOS | CocoaPods dependencies | iOS version, pod conflicts |
text | iOS | Open this in Xcode | Always (not .xcodeproj) |
text | iOS | Xcode build config | Signing, capabilities |
| Platform Channels | Both | Flutter ↔ Native bridge | Native API access |