# 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
    

<mark>Flutter's builtin platform-specific API support doesn't rely on code generation, but rather on a flexible message passing style</mark>

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:

![](https://docs.flutter.dev/assets/images/docs/PlatformChannels.png align="left")

**Observations from the architecture:** - On Flutter side `MethodChannel` enables sending messages that correspond to method calls. - On the platform side, `MethodChannel` on Android [MethodChannelAndroid](https://api.flutter.dev/javadoc/io/flutter/plugin/common/MethodChannel.html) and `FlutterMethodChannel` on iOS [MethodChanneliOS](https://api.flutter.dev/ios-embedder/interface_flutter_method_channel.html) 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.

| 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

### Source Code:

You can find the source code here: [battery\_level\_tutorial](https://github.com/khkred/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 <mark>battery</mark> so our domain prefix would be. `com.harishkunchala` so our <mark>total channel name</mark> 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:
    

```dart
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:
    

```dart
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.
    

```dart
// 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.

```dart
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:
    

```kotlin
private val CHANNEL = "com.harishkunchala/battery";
```

* Also override `configureFlutterEngine` method and create a `MethodChannel` and call `setMethodCallHandler()`. Here's the entire code:
    

```kotlin
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:
    

```kotlin
import android.content.Context  
import android.os.BatteryManager
```

* Now add the following method below in the `MainActivity` class below `configureFlutterEngine()` method:
    

```kotlin
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()`:

```kotlin
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 &gt; 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 &gt; Runner** in the Project navigator.
    
2. Open the file `AppDelegate.swift` located under **Runner &gt; Runner** in the Project navigator.
    

#### Steps:

* Override the `application:didFinishLaunchingWithOptions:` function and create a `FlutterMethodChannel` tied to the channel name `com.harishkunchala/battery`:
    

```swift
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`
    

```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:
    

```swift
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:

![](https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExYm8xMzlzMGF6OWh5ODI2YnRwbWtmcWRhbWU2bThwZjJlNHBic296cCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/6s3lzKZoCFymz3kB1B/giphy.gif align="left")

Finally our app is working perfectly. You can find the entire source code for the app here: [battery\_level\_tutorial](https://github.com/khkred/battery_level_tutorial)

Happy Coding 🐦
