How to Integrate Platform Channels in Flutter : A Step-by-Step Tutorial

Raju Potharaju
GYTWorkz
Published in
6 min readMar 9, 2023

--

Blog Banner

Flutter is a cross-platform toolkit, yet it allows you the full power of native platform APIs in a very simple way. Even if you don’t have any past experience writing native code, be scared no more.

In this blog post, we will explore platform channels in Flutter and how they can be used to interact with platform-specific features or native APIs.

PS: You can find the code for this article in lib/platform_channels folder in my Github repo.

What are Platform Channels?

Platform channels are a feature in Flutter that allows developers to interact with platform-specific code. With platform channels, developers can send messages from their Flutter app to the native code on the platform, and vice versa. This makes it possible to access platform-specific features that are not available through Flutter’s core APIs.

Why Platform Channels?

Let’s imagine a common use-case. A client wants to integrate with a 3rd-party provider. This provider doesn’t have support for Flutter yet, but they have an iOS and Android SDK. In such case, you will need to call these SDKs from Flutter.

For example, you may want to access the device’s camera or microphone, or access a native library that is not available in Flutter. Platform channels make it possible to access these features by creating a bridge between the Flutter app and the native code on the platform.

Example: Platform Channel Bridge

Setting up Platform Channels

To use platform channels in Flutter, you need to define a channel (bridge) that connects your Flutter app to the native code. The channel is defined by a name, this name is a String, if you misspelled it in either side of the channel, you won’t be able to reach out to the other side.

There are 2 types of platform channels:

  • MethodChannel: a channel used to communicate with the platform using asynchronous method calls. Think of it as a Future.
  • EventChannel: a channel used to listen to event streams from the platform. Think of it as a Stream.

MethodChannel

Step 1: Create the Flutter platform client

// Give it a name.
const _channelName = 'app.id.com/my_channel_name';

// Construct it.
final _channel = const MethodChannel(_channelName);

Once you have a MethodChannel object ready, you can call any method by its name:

dynamic returnValue = await methodChannelPlatform.invokeMethod("addnumbers");

if you want to pass arguments to this method you can do it as follows.

    dynamic returnValue = await methodChannelPlatform
.invokeMethod("addnumbers", <String, dynamic>{
'n1': 50,
'n2': 10,
});

In this example we define channel name as ‘app.id.com/my_channel_name’ and method name as “addnumbers” which is first argument and second argument being the values that you want to pass to android/iOS side, here we are passing n1 and n2.

Complete code from Flutter side looks like this.

  dynamic additionResult = 0;

static const methodChannelPlatform =
MethodChannel("app.id.com/my_channel_name");

void _invokeAddNumbers() async {
dynamic returnValue = await methodChannelPlatform
.invokeMethod("addnumbers", <String, dynamic>{
'n1': 50,
'n2': 10,
});
setState(() {
additionResult = returnValue;
});
}

Step 2: Android platform-specific implementation

  1. Start android Studio
  2. Open the file MainActivity.kt located in the my_flutter_app/android/app/src/main/kotlin/ folder in the Project view.
  3. Inside the configureFlutterEngine() method, create a MethodChannel and call setMethodCallHandler(). Make sure to use the same channel name as was used on the Flutter client side.
class MainActivity: FlutterActivity() {
private val channel = "app.id.com/my_channel_name";

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor, channel)
.setMethodCallHandler { call, result ->
// execute android side code here
}
}
}

Here we will implement Kotlin-level logic to access the method invoked from flutter, process, and return the response back to Flutter.

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor, channel)
.setMethodCallHandler{
call, result ->
if(call.method == "addnumbers"){
val sum: Int = addNumbers(call); // function is written separately,
result.success(hashMapOf(
"Sum" to sum,
));
}else{
result.notImplemented()
}

}
}

In the above example, call.method == “addnumbers”, the identifier “addnumbers” has to be the same as the method name declared on flutter side.

The complete code from Android side is as follows:

class MainActivity: FlutterActivity() {
private val channel = "app.id.com/my_channel_name";

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor, channel)
.setMethodCallHandler { call, result ->
if (call.method == "addnumbers") {
val sum: Int = addNumbers(call); // function is written separately,
result.success(
hashMapOf(
"Sum" to sum,
)
);
} else {
result.notImplemented()
}

}
}

private fun addNumbers(call: MethodCall): Int {
val args = call.arguments as Map<String, Any>
val a = args["n1"] as Int;
val b = args["n2"] as Int;
val sum: Int = a + b;
return sum;
}

}

Here we are accessing the “n1” and “n2” values sent from flutter, processing them, and returning the response in form of JSON.

Step 3: iOS platform-specific implementation

  1. Start Xcode
  2. Open ios folder in your flutter project in Xcode.
  3. Open the file AppDelegate.swift located under Runner > Runner in the Project navigator.
  4. Override the application:didFinishLaunchingWithOptions: function and create a FlutterMethodChannel tied to the channel name app.id.com/my_channel_name.
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {

let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let myChannnel = FlutterMethodChannel(
name: "app.id.com/my_channel_name",
binaryMessenger: controller.binaryMessenger)

myChannnel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
// execute iOS side code here
})

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

Here we will implement iOS-level logic to access the method invoked from flutter, process, and return the response back to Flutter.

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {

let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let myChannnel = FlutterMethodChannel(
name: "app.id.com/my_channel_name",
binaryMessenger: controller.binaryMessenger)

myChannnel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in

if(call.method == "addnumbers"){
let c: Int = self.addNumber(call: call) // function is written separately
let finalResult: [String: Int] = ["Sum" : c]
result(finalResult)
}else{
result(FlutterMethodNotImplemented)
}
})


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

In the above example, call.method == “addnumbers”, the identifier “addnumbers” has to be the same as the method name declared on the flutter side.

The complete code from iOS side is as follows:

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {

let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let myChannnel = FlutterMethodChannel(
name: "app.id.com/my_channel_name",
binaryMessenger: controller.binaryMessenger)

myChannnel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in

if(call.method == "addnumbers"){
let c: Int = self.addNumber(call: call) // function is written separately
let finalResult: [String: Int] = ["Sum" : c]
result(finalResult)
}else if(call.method == "getbatterylevel"){
self.receiveBatteryLevel(result: result)
}else{
result(FlutterMethodNotImplemented)
}
})


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


func addNumber(call: FlutterMethodCall) -> Int {
let args = call.arguments as? Dictionary<String, Any>
let a = args?["n1"] as! Int
let b = args?["n2"] as! Int
print(a as Int)
print(b as Int)
let c = a + b
return c;
}
}

Here we are accessing the “n1” and “n2” values sent from flutter, processing them and returning the response in form of json.

When the native code receives the message, it can perform the necessary actions and send a response back to the Flutter app. In this case, the native code would return the battery level as an integer, which is then converted to a string and displayed in the UI.

Conclusion

In this article, we learned how to create the bridge or channel between Flutter ←→ Android and Flutter ←→ iOS and address problems by communicating with the native code.

Platform channels are a powerful feature in Flutter that enables us to interact with platform-specific code. We can create high-quality, cross-platform applications that take full advantage of the features of each platform.

❤ ❤ Thanks for reading this article ❤❤

If you find this blog informative do give a clap 👏 below.

Let's connect on LinkedIn.

Read my other blogs here:

--

--

Raju Potharaju
GYTWorkz

Software Engineer with 3 years of experience building beautiful and complex Applications | Loves Teaching | Talks about Flutter, React, Python and Java