Introduction

PlatformException is thrown when native platform code (Android Kotlin/Java or iOS Swift/Objective-C) returns an error through a Flutter method channel. This happens when the native implementation encounters an error and calls result.error() on Android or throws a FlutterError on iOS. Understanding the error code and message is key to resolving the issue. Method channels are the primary communication bridge between Flutter's Dart code and platform-specific native code, making them critical for accessing device features like camera, GPS, sensors, and platform APIs. When this bridge fails, the entire feature breaks, and the error must be traced across both the Dart and native layers.

Symptoms

  • Console error:
  • `
  • PlatformException(channel-error, Unable to establish connection on channel: "com.example/plugin".
  • Details: error code 12, null, null)
  • `
  • Or:
  • `
  • PlatformException(PERMISSION_DENIED, Location permission denied, null, null)
  • `
  • Stack trace points to MethodChannel.invokeMethod

Common Causes

  • Native code explicitly returns an error via result.error(code, message, details)
  • Required platform permissions not granted (camera, location, storage)
  • Native SDK not properly initialized in MainActivity or AppDelegate
  • Method name mismatch between Dart and native implementations
  • Missing dependencies in build.gradle or Podfile

Step-by-Step Fix

  1. 1.Extract full error details in Dart:
  2. 2.```dart
  3. 3.try {
  4. 4.final result = await methodChannel.invokeMethod('getLocation');
  5. 5.} on PlatformException catch (e) {
  6. 6.print('Error code: ${e.code}');
  7. 7.print('Error message: ${e.message}');
  8. 8.print('Error details: ${e.details}');
  9. 9.print('Stack trace: ${e.stacktrace}');
  10. 10.}
  11. 11.`
  12. 12.Check Android native implementation (MainActivity.kt):
  13. 13.```kotlin
  14. 14.MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example/plugin")
  15. 15..setMethodCallHandler { call, result ->
  16. 16.when (call.method) {
  17. 17."getLocation" -> {
  18. 18.if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
  19. 19.!= PackageManager.PERMISSION_GRANTED) {
  20. 20.result.error(
  21. 21."PERMISSION_DENIED",
  22. 22."Location permission not granted",
  23. 23.null
  24. 24.)
  25. 25.return@setMethodCallHandler
  26. 26.}
  27. 27.val location = fusedLocationClient.lastLocation
  28. 28.result.success(location)
  29. 29.}
  30. 30.else -> result.notImplemented()
  31. 31.}
  32. 32.}
  33. 33.`
  34. 34.Check iOS native implementation (AppDelegate.swift):
  35. 35.```swift
  36. 36.let controller = window?.rootViewController as! FlutterViewController
  37. 37.let channel = FlutterMethodChannel(name: "com.example/plugin",
  38. 38.binaryMessenger: controller.binaryMessenger)
  39. 39.channel.setMethodCallHandler { (call, result) in
  40. 40.switch call.method {
  41. 41.case "getLocation":
  42. 42.if CLLocationManager.locationServicesEnabled() {
  43. 43.self.locationManager.requestLocation()
  44. 44.result.success(true)
  45. 45.} else {
  46. 46.result(FlutterError(code: "SERVICES_DISABLED",
  47. 47.message: "Location services are disabled",
  48. 48.details: nil))
  49. 49.}
  50. 50.default:
  51. 51.result(FlutterMethodNotImplemented)
  52. 52.}
  53. 53.}
  54. 54.`
  55. 55.Verify permissions in AndroidManifest.xml:
  56. 56.```xml
  57. 57.<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  58. 58.<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  59. 59.`
  60. 60.Verify permissions in Info.plist:
  61. 61.```xml
  62. 62.<key>NSLocationWhenInUseUsageDescription</key>
  63. 63.<string>This app needs access to location for mapping features.</string>
  64. 64.`
  65. 65.Handle permission request before channel call:
  66. 66.```dart
  67. 67.import 'package:permission_handler/permission_handler.dart';

Future<void> getLocation() async { final status = await Permission.location.request(); if (!status.isGranted) { throw Exception('Location permission denied'); } final result = await methodChannel.invokeMethod('getLocation'); return result; } ```

Prevention

  • Always request permissions before calling native methods that require them
  • Wrap all invokeMethod calls in try-catch with PlatformException handling
  • Log the full PlatformException details (code, message, details) for debugging
  • Test on both Android and iOS simulators and real devices
  • Use flutter run --verbose for detailed channel communication logs