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.
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 😄