When we are working with a cross-platform framework, it isn’t possible to guarantee that we will be working only with that framework without any work in the native code. Developers will most likely resort to native either because of features that the framework hasn’t yet provided or because of performance issues that can only be solved in native. Platform channels are the method in which you can write platform-specific native code to use in your Flutter applications.

The standard platform channels use a standard message codec that supports efficient binary serialization of simple JSON-like values, such as booleans, numbers, Strings, byte buffers, and Lists and Maps of these. The serialization and deserialization of these values to and from messages happens automatically when you send and receive values.

Platform Channel consists of three parts: MethodChannel class on Flutter, MethodChannel class on Android and FlutterMethodChannel class on iOS.

The advantage of Flutter is that uses a flexible system that allows you to call platform-specific APIs whether available in Kotlin or Java code on Android, or in Swift or Objective-C code on iOS.

This tutorial show how to create Flutter function that calls Android and iOS native code.

Calling native from Flutter

Application will be calling a function according to its name and the program will look for its implementation in the Android or the iOS code.

First, create a MethodChannel.

The client and host sides of the channel are connected through the channel name passed in the channel constructor. All channel names used in a single app must be unique.

static final channelName = 'example.channel/native';
const methodChannel = MethodChannel(channelName);

Then, create method that will call specifying method on the platform channel.

Future<void> _getBatteryLevel() async {
    String batteryLevel;
    try {
        batteryLevel = await platform.invokeMethod('getBatteryLevel');
    } on PlatformException catch (e) {
        batteryLevel = e.message;
    }
}

Android

Navigate to directory holding Flutter app, and open android folder in Android Studio.

Open file MainActivity.kt. Create the same channel that you have declared in your flutter part.

private val channelName ='example.channel/native'

Then inside configureFlutterEngine() add method call handler.

MethodChannel(flutterEngine.dartExecutor.binaryMessage, channelName)
    .setMethodCallHandler { call, result ->
        if (call.method == "getBatteryLevel") {
            val batteryLevel = getBatteryLevel()
            result.success(batteryLevel)
        } else {
            result.notImplemented()
        }
    }

Next, add the implementation of the method you want to call (in the example, the function getBatteryLevel() ).

iOS

Open ios folder in XCode. Go to file AppDelegate.swift and inside application:didFinishLaunchingWithOptions function, create channel.

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    let channelName = "example.channel/native"
    let rootViewController : FlutterViewController = window?.rootViewController as! FlutterViewController
    let methodChannel = FlutterMethodChannel(name: channelName, binaryMessage: rootViewController)

    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)

  }

Next, add method call handler.

methodChannel.setMethodCallHandler( {(call: FlutterMethodCall, result: FlutterResult) -> Void in
    guard call.method == "getBatteryLevel" else {
        result(FlutterMethodNotImplemented)
        return
    }
    self?.getBatteryLevel(result: result)
})

Create method implementation (in example getBatteryLevel).

Reverse Example

If you need to call a Flutter method from native code then you call the same methods from earlier, but vice versa.

Flutter code

static final channelName = 'example.channel/native'; 
const methodChannel = MethodChannel(channelName);
methodChannel.setMethodCallHandler(_nativeMethodCallHandler);

Future<dynamic> _nativeMethodCallHandler(MethodCall call) async {
    switch(call.method) {
        case "exampleMethod":
            //function call
    }
}
 

Android code

val channelName = 'example.channel/native'
val methodChannel = MethodChannel(flutterView, channelName)

methodChannel.invokeMethod("exampleMethod")

iOS code

let rootViewController : FlutterViewController = window?.rootViewController as! FlutterViewController
let channelName ="example.channel/native"
let methodChannel = FlutterEventChannel(name: channelName, binaryMessenger: rootViewController)

methodChannel.invokeMethod("exampleMethod")