Skip to main content

Command Palette

Search for a command to run...

How to use Platform Channels in Flutter

Updated
7 min read
How to use Platform Channels in Flutter

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.

Platform Channels Architecture:

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.

Getting Battery Life of the device using Platform Channels

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.

PlatformAPI
AndroidBatteryManager
iOSdevice.batteryLevel
WindowsGetSystemPowerStatus
LinuxUPower
We are going to call all of this with a Single Platform Message: getBatteryLevel()

For now we'll stick with Android and iOS

Source Code:

You can find the source code here: battery_level_tutorial

Step 1: Create a new App

In terminal run the following: flutter create battery_level_tutorial

Step 2: Create the Flutter Platform Client

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:

  • We can initialize our method channel in the 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),
          ],
        ),
      ),
    );
  }

Step 3: Add an Android Specific Implementation:

  • 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";
  • Also override 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
  • Now add the following method below in the 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()  
    }  
}

Step 4: Add an iOS platform-specific implementation

Start by opening the iOS host portion of your Flutter app in Xcode:

  1. Start Xcode.

  2. Select the menu item File > Open....

  3. 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:

  1. Expand Runner > Runner in the Project navigator.

  2. Open the file AppDelegate.swift located under Runner > Runner in the Project navigator.

Steps:

  • Override the 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)
  }
}
  • Next let's create a method receiveBatteryLevel() that's retrieve iOS battery level. So add the following code at the bottom of AppDelegate.swift
private 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'.

Testing the code:

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 🐦

More from this blog

Harish Learns Code

46 posts

Flutter SDK Engineer at Esri. I mostly write about Flutter and Rust.