Using Flutter’s MethodChannel to invoke Native code in Android and iOS

Using Flutter’s MethodChannel to invoke Native code in Android and iOS

While writing a Flutter application we have observed many times there is a feature to build for which we need help from the native platform, we need to write a method to use native API to establish a connection between flutter and the native side.

Flutter provides a way by which we can call platform-specific APIs available in Kotlin or Java code on Android and in Swift or Objective C code on iOS.

To acheive this from the Flutter application, we have to send messages to a host on Android or iOS parts of the app over a platform channel. The host listens on the platform channel and receives the message. It then uses any platform-specific APIs using the native programming language and sends back a response to the Flutter portion of the app.

Communication between Dart and native code

Messages are passed between the Flutter Code(UI) and host (platform) using platform channels. Messages and responses are passed asynchronously and the user interface remains responsive.

In this blog, I will be explaining how we can call Kotlin and Swift code from the Dart side via the Flutter platform channels’ MethodChannel class.

But Before proceeding first, let’s start with some of the highlighted features of MethodChannel class.

In Flutter we have MethodChannel Dart class which developers can invoke platform-specific native code from the Flutter environment. Also, Flutter gives us the required APIs to send data back to Flutter from the native host app code.

Cross-platform support

We’ll discuss how to invoke Kotlin code from Flutter, but the Flutter framework implementation lets us call native code on iOS, Linux, macOS, and Windows. So, calling Swift/Objective-C and C/C++ code is also possible.

Bi-directional communication support

The Dart-based MethodChannel class helps you call platform-specific code from Flutter. On the other hand, Flutter exposes platform-specific MethodChannel API to call Dart code from the native side. So, we can make a bi-directional communication line between your Dart code and platform-specific native code.

Error handling features

Platform-specific APIs typically throw exceptions or return error codes for unexpected or failure events. On the native side, we can easily use native SDK error-handling strategies.

The MethodChannel comes with inbuilt-error handling support and throws Dart exceptions. Also, we can use error codes from exception instances to improve the error-handling strategy if you want.

So let's get started with practical applications!

In this section, we’ll build a sample Flutter app and extend it according to various practical requirements to understand how to use MethodChannel for connecting Kotlin and Dart together.

Let’s start with a simple task ie. generate a random number using Kotlin standard libraries and pass that generated number from Kotlin to Dart side using MethodChannel.

Step - 1: Create a new Flutter project with the following command:

flutter create cropsly_flutter_platform_channels_demo
cd croplsly_flutter_platform_channels_demo

Step - 2: Create a platform Channel

We will create a MethodChannel instance in the Dart environment as follows:

static const platform = MethodChannel('cropsly.com/channel');

Step - 3: Invoke method on platform Channel in dart code

String response = "";
  try {
    final String result = await  platform.invokeMethod('getRandomNumber');
    response = result;
  } on PlatformException catch (e) {
    response = "Failed to Invoke: '${e.message}'.";
  }

Step - 4: Android - Register a setMethodCallHandler in Kotlin

MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "cropsly.com/channel").setMethodCallHandler {
    call, result ->
    if(call.method == "getRandomNumber") {
        val rand = Random.nextInt(100)
        result.success(rand)
    }
    else {
        result.notImplemented()
    }
}

Here, we have used the result.success method to return the generated random number to the Dart environment.

Step - 5: iOS - Create FlutterMethodChannel in Swift

let channel = FlutterMethodChannel(name: "cropsly.com/channel",
                                              binaryMessenger: controller.binaryMessenger)
    channel.setMethodCallHandler({
    [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
              
    guard call.method == "getRandomNumber" else {
      result(FlutterMethodNotImplemented)
      return
    }

    return result(Int.random(in: 0..<200))
})

Here, we have used the result method to return the generated random number to the Dart environment.

Complete Code:

Flutter Code

import 'package:flutter/services.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Cropsly - Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Cropsly - Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({
    required this.title,
  });

  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  static const platform = MethodChannel('cropsly.com/channel');
  Future<void> _generateRandomNumber() async {
    int random;
    try {
      random = await platform.invokeMethod('getRandomNumber');
    } on PlatformException catch (e) {
      random = 0;
    }
    setState(() => _counter = random);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('Kotlin generates the following number:'),
            Text('$_counter', style: Theme.of(context).textTheme.headline4),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _generateRandomNumber,
        tooltip: 'Generate',
        child: const Icon(Icons.refresh),
      ),
    );
  }
}

Android Native code

import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "cropsly.com/channel").setMethodCallHandler {
      call, result ->
        if(call.method == "getRandomNumber") {
          val rand = Random.nextInt(200)
          result.success(rand)
        }
        else {
          result.notImplemented()
        }
    }
  }
}

iOS Native code

import UIKit
import 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 channel = FlutterMethodChannel(name: "cropsly.com/channel",
                                              binaryMessenger: controller.binaryMessenger)
   
              channel.setMethodCallHandler({
              [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
              
              guard call.method == "getRandomNumber" else {
               result(FlutterMethodNotImplemented)
               return
              }

            return result(Int.random(in: 0..<200))
          })

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

We invoke the getRandomNumber method when the user presses the floating action button. When the particular method gets invoked, the app sends a message to the method channel via platform.invokeMethod(‘getRandomNumber’).

Internally, the Flutter engine triggers the method channel handler and executes the Kotlin or Swift based random number generation code we wrote before.

Result

Run the code on Android and iOS and click on the FloatingActionButton to see the result

Happy learning 😄