How to use Platform Channels in Flutter

Search for a command to run...

No comments yet. Be the first to comment.
While I generally write about Flutter. This problem has been bugging me for a while and there seem to be limited resources to find a solution. So for anyone who is looking for a solution. Here you go: Type edge:flags into the address bar and open it...
In this tutorial, we’ll go through covering interceptor, which is a very crucial feature of Dio. You can find the entire source code for the project here: dio_tasker Understanding Interceptors: What is an interceptor? An interceptor in Dio is a powe...

In this part, we’ll enhance our Task Manager app by parsing JSON responses and handling different types of errors. We’ll use Dio’s built-in error handling mechanisms. You can find the source code of the entire app here: dio_tasker Parsing JSON Respon...

While building apps in Flutter is extremely fluid and wonderfully intuitive. We obviously know that we can’t build the next TikTok or Twitter by only building offline apps. Which is why we absolutely need internet capabilities in our Flutter apps. Th...

Understanding when to use setState in Flutter is crucial for managing our app’s state effectively. Here’s a detailed guide: When to use setState ? Updating the UI: Use setState when we need to update the UI in response to changes in the internal sta...

Platform channels in Flutter uses a flexible system that allows us to call platform-specific APIs in a language that works directly with those APIs:
Kotlin or Java on Android
Swift or Objective-C on iOS
C++ on Windows
Objective-C on macOS
C on Linux
Flutter's builtin platform-specific API support doesn't rely on code generation, but rather on a flexible message passing style
This is the way it works:
The Flutter portion of the app sends messages to its host, the non-Dart portion of the app, over a platform channel.
The host listens on the platform channel, and receives the message. It then calls into any number of platform-specific APIs—using the native programming language—and sends a response back to the client, the Flutter portion of the app.

Observations from the architecture: - On Flutter side MethodChannel enables sending messages that correspond to method calls. - On the platform side, MethodChannel on Android MethodChannelAndroid and FlutterMethodChannel on iOS MethodChanneliOS enable receiving method calls and sending back a result. These classes allow you to develop a platform plugin with very little 'boilerplate' code.
Let's create an app that gets Battery life of the device. We are going to implement it in all the platforms. Here are the platforms along with their associated APIs.
| Platform | API |
| Android | BatteryManager |
| iOS | device.batteryLevel |
| Windows | GetSystemPowerStatus |
| Linux | UPower |
We are going to call all of this with a Single Platform Message: getBatteryLevel() |
For now we'll stick with Android and iOS
You can find the source code here: battery_level_tutorial
In terminal run the following: flutter create battery_level_tutorial
As we know the app's State class holds the current app state. So let's extend that to hold the current battery state.
First, construct the channel: In order to do that we need to define a single method called MethodChannel() that returns the battery level.
The client and host sides of a channel are connected through a channel name passed in the constructor. All channel names used in a single app must be unique; So we just have to prefix the channel name with a unique domain prefix.
Our Channel Name: For example, let's say our channel name is battery so our domain prefix would be. com.harishkunchala so our total channel name is com.harishkunchala/battery
Current Imports:
Remember platform values are going to be always either async or streams.
Since we are going to call our battery percentage through a trigger like clicking a button. It is async value. Hence here are our imports:
import 'dart:async'; // As battery value is async
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // For calling method channel
Initialize our Method Channel:
HomePage class like this:class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel('com.harishkunchala/battery');
....
Next, invoke a method on the method channel, specifying the concrete method to call using the String identifier getBatteryLevel.
The call might fail—for example, if the platform doesn't support the platform API (such as when running in a simulator), so wrap the invokeMethod call in a try-catch statement.
// Get Battery Level
String _batteryLevel = 'Unknown battery level.';
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final result = await platform.invokeMethod<int>('getBatteryLevel');
batteryLevel = 'Battery level at $result %';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
setState(() {
_batteryLevel = batteryLevel;
});
}
Update the build method: We'll create a simple UI with a basic column with a button and text to show the battery percentage.
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
ElevatedButton(
onPressed: _getBatteryLevel,
child: const Text('Get Battery Level')),
const SizedBox(height: 20),
Text(_batteryLevel),
],
),
),
);
}
First open android/ folder in Android Studio.
Navigate to Kotlin folder and open MainActivity.kt
Inside our MainActivity define a String variable CHANNEL and set it to exactly what we used in our flutter:
private val CHANNEL = "com.harishkunchala/battery";
configureFlutterEngine method and create a MethodChannel and call setMethodCallHandler(). Here's the entire code:package com.example.battery_level_tutorial
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.harishkunchala/battery"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler{
call, result ->
// This method is invoked on the main thread
// TODO
}
}
}
Add the Android Kotlin code that uses the Android battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native Android app.
First let's add the needed imports at the top of the file:
import android.content.Context
import android.os.BatteryManager
MainActivity class below configureFlutterEngine() method:private fun getBatteryLevel(): Int {
val batteryLevel: Int
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
return batteryLevel
}
Call setMethodCallHandler():
We need to handle a single platform method, getBatteryLevel()
so let's test for that in the call argument.
The implementation of this platform method calls the Android code written in the previous step.
It returns a response for both the success and error cases using the result argument.
If an unknown method is called, report that instead
So here's our final MethodChannle():
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// This method is invoked in the main thread
call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available", null)
}
} else {
result.notImplemented()
}
}
Start by opening the iOS host portion of your Flutter app in Xcode:
Start Xcode.
Select the menu item File > Open....
Navigate to the directory holding your Flutter app, and select the ios folder inside it. Click OK.
Add support for Swift in the standard template setup that uses Objective-C:
Expand Runner > Runner in the Project navigator.
Open the file AppDelegate.swift located under Runner > Runner in the Project navigator.
application:didFinishLaunchingWithOptions: function and create a FlutterMethodChannel tied to the channel name com.harishkunchala/battery:import Flutter
import UIKit
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(name: "com.harishkunchala/battery",
binaryMessenger: controller.binaryMessenger)
batteryChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
// This method is invoked on the UI thread.
// Handle battery messages.
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
receiveBatteryLevel() that's retrieve iOS battery level. So add the following code at the bottom of AppDelegate.swiftprivate func receiveBatteryLevel(result: FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
if device.batteryState == UIDevice.BatteryState.unknown {
result(FlutterError(code: "UNAVAILABLE", message: "Battery level not available.", details: nil))
} else {
result(Int(device.batteryLevel * 100))
}
}
Finally complete the setMethodCallHandler() method added earlier.
We need to handle a single platform method getBatteryLevel(), so test for that in the call argument.
The implementation of this platform method calls the iOS code written in the previous step. If an unknown method is called, report that instead:
batteryChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
// This method is invoked on the UI thread.
guard call.method == "getBatterLevel" else {
result(FlutterMethodNotImplemented)
return
}
self?.receiveBatteryLevel(result: result)
})
You should now be able to run the app on iOS. Note: If you are using iOS simulator, it doesn't support battery APIs, and the app displays 'Battery level not available'.
Perfect, for now let's test it on an actual Android device. Here's the output:

Finally our app is working perfectly. You can find the entire source code for the app here: battery_level_tutorial
Happy Coding 🐦