Working with native elements in Flutter: Platform Channel vs Pigeon vs Foreign Function Interface (FFI)

Dart and Flutter combine to offer a robust solution for developing a wide range of software applications. This powerful pairing is not only effective for creating mobile applications on Android and iOS but also extends its capabilities to desktop applications across Linux, Windows, and macOS.

Table of Contents

Understanding Flutter’s Flexibility and Platform Integration

Flutter is typically equipped to handle the majority of features required by modern applications. However, certain scenarios necessitate deeper integration with underlying platform-specific components. Fortunately, Flutter facilitates smooth and straightforward communication processes between its own environment and the platform it operates on.

Working with native elements in Flutter: Platform Channel vs Pigeon vs Foreign Function Interface (FFI)

Key Concepts and Practical Application

What You Will Learn

In this article, we dive deep into the specifics of integrating Flutter with native platform components. By the end, you will understand:

  • The appropriate situations for implementing communication between Flutter and native components.
  • Key technologies such as platform channels, pigeon, and foreign function interface (FFI).
  • How to implement these communication channels within your applications.
  • Real-world use cases demonstrating these mechanisms in action.

This piece is designed to be interactive, offering you the opportunity to apply what you learn through a practical demonstration. You’ll be guided to build a simple demo application that allows users to create a text file on their device using inputs they define. This application will be compatible with Android, iOS, and macOS.

Implementing the Demo Application

We will start by utilizing method channels to develop this feature for Android and iOS. Subsequently, we will switch our approach to employ pigeon, and finally, we will leverage FFI to enable the feature on macOS.

Join Our Whatsapp Group

Join Telegram group

Requirements for macOS Users

To set up and use FFI for macOS, it is essential that you have access to a MacBook and have Xcode installed on your machine.

Who Should Read This Article?

This guide is intended for mid to senior-level Flutter developers who are looking to enhance their skills in Flutter’s advanced integration capabilities. By following this guide, developers will gain hands-on experience and deeper insights into Flutter’s versatile framework and its interaction with native platform features.

For those new to these concepts or seeking additional resources, Flutter’s official documentation provides comprehensive guides and tutorials on how to use platform channels, pigeon, and using FFI to communicate with native code.

Getting Started: Setting Up the Flutter Project

To begin, clone the Flutter project using the following command:

git clone git@github.com:JasperEssien2/text_editors.git -b "method-channel-tasks"

Exploring the Project Structure

In the lib/main.dart file, you’ll find the entry point of the Flutter application, which includes the MyHomePage widget. When a user taps on the save button within this widget, it triggers the saveFile() method of the SaveFileService class.

Understanding the SaveFileService Abstraction

The SaveFileService is an abstract class that we define to serve as a wrapper around communication code. Its purpose is to facilitate easy switching between different communication implementations. For each communication mechanism we add, we implement this abstract class.

abstract class SaveFileService {
  FutureOr<bool> saveFile({required String fileName, required String text});
}

Knowing When to Establish Communication

Identifying the Need

There are certain scenarios where establishing communication between Flutter and native components becomes necessary. This is particularly relevant when integrating platform-specific features such as geolocation, alarms, sensors, notifications, sound, camera functionalities, device information, and more.

Exploring Available Solutions

It’s worth noting that in many cases, there may already be existing plugins or packages available that address your specific requirements. These can significantly streamline the development process by providing pre-built solutions for common tasks.

Choosing the Right Communication Mechanism

Introduction to Platform Channels

One option for achieving communication between Flutter and native components is through the use of platform channels. Platform channels provide a seamless way to exchange data and invoke platform-specific functionality from within Flutter.

By delving into platform channels, developers can tap into the full potential of Flutter’s cross-platform capabilities while still harnessing the power of native features when necessary.

Join Our Whatsapp Group

Join Telegram group

Understanding Platform Channels in Flutter

Platform channels serve as a vital mechanism for facilitating communication between Flutter and the host platform. This bi-directional communication process involves sending messages from Flutter to the platform and receiving responses to handle necessary logic, whether it’s successful execution or error handling.

Two Major Implementations of Platform Channels

In Flutter, there are two primary implementations of platform channels:

  1. Method Channel:
  • This named channel processes messages asynchronously and is ideal for one-time tasks. For example, retrieving device battery status.
  • Our focus will be on this implementation.
  1. Event Channel:
  • Another named channel that processes messages using a stream. It’s best suited for tasks requiring frequent updates, such as listening to a device’s motion sensor.

Implementing the Method Channel

Dart Implementation

To set up communication on the Dart side, navigate to lib/save_file_service.dart. In the MethodChannelService class, there are certain tasks to handle.

Setting Up the Method Channel

Begin by creating a channel for communication. Replace TODO1 with the following code snippet:

static const _methodChannel = MethodChannel("com.example.text_editors/action");

By creating an instance of MethodChannel, we establish a pipeline that connects Flutter with the host platform.

Understanding MethodChannel Constructor Parameters

The MethodChannel constructor takes three parameters:

  1. String name:
  • A unique identifier for the channel. This name must match its native counterpart and be unique for each channel created.
  1. MethodCodec codec:
  • Different platforms use different data types. For seamless communication, data is transformed and sent as byte data, allowing the receiving platform to interpret it appropriately. The MethodCodec handles this transformation.

Invoking Methods via the Method Channel

Now that we’ve established a channel for communication, the next step is to utilize this channel to invoke a method. Replace TODO2 in the saveFile() method with the following code snippet:

/// Wrap invocation in a try-catch  
try {
  /// Invoke a method and pass in necessary arguments, await a response
  return await _methodChannel.invokeMethod(
    "createFile",
    {
      'fileName': fileName,
      'content': text,
    },
  );
} catch (e) {
  /// Return false when an error occurs to signify that attempt to save the file failed
  return false;
}

With this implementation, when the saveFile() method is called, it requests the “createFile” action to be handled by the host platform, sending along the file name and content.

Join Our Whatsapp Group

Join Telegram group

Understanding Method Channel Invoke Methods

The method channel provides three invoke methods:

  • invokeMethod<T>(): Sends a message through the channel and expects a future single-type value.
  • invokeMapMethod<K, V>(): Sends a message through the channel and expects a future map with keys and values.
  • invokeListMethod<List<T>(): Sends a message through the channel and expects a future list of type T.

Exception Handling

We wrap our method invocation in a try-catch block because two exceptions can occur:

  1. PlatformException: Triggered when the invocation fails on the platform host.
  2. MissingPluginException: Triggered when the host counterpart hasn’t implemented the method name.

Implementing Method Channel on Host Platforms

Android Implementation

Navigate to /android/app/src/main/kotlin/com/example/text_editors/MainActivity.kt. The MainActivity in Android is the initial screen that appears when the Android app is launched.

To create a method channel in Android, we need access to the FlutterEngine. Override the configureFlutterEngine() method to gain this access.

Replace TODO 1 with the following code snippet:

val methodChannel = MethodChannel(
        flutterEngine.dartExecutor.binaryMessenger,
        channelName
    )

Here, we create an instance of MethodChannel, which requires a BinaryMessenger. We obtain an instance from the dart executor of the flutter engine. Then, we pass in a channelName, which must correspond to its Dart counterpart.

Setting Up Method Channel

Now that we’ve established a channel for communication, the next step is to use this channel to invoke a method. Replace TODO2 in the saveFile() method with the following code snippet:

/// Wrap invocation in a try-catch  
try {
  /// Invoke a method and pass in necessary arguments, await a response
  return await _methodChannel.invokeMethod(
    "createFile",
    {
      'fileName': fileName,
      'content': text,
    },
  );
} catch (e) {
  /// Return false when an error occurs to signify that the attempt to save the file failed
  return false;
}

Understanding Method Channel Invoke Methods

The method channel provides three invoke methods:

  • invokeMethod<T>(): Sends a message through the channel and expects a future single-type value.
  • invokeMapMethod<K, V>(): Sends a message through the channel and expects a future map with keys and values.
  • invokeListMethod<List<T>(): Sends a message through the channel and expects a future list of type T.

Join Our Whatsapp Group

Join Telegram group

Exception Handling

We wrap our method invocation in a try-catch block because two exceptions can occur:

  1. PlatformException: Triggered when the invocation fails on the platform host.
  2. MissingPluginException: Triggered when the host counterpart hasn’t implemented the method name.

Implementing Method Channel on Host Platforms

Android Implementation

Navigate to /android/app/src/main/kotlin/com/example/text_editors/MainActivity.kt. The MainActivity in Android is the initial screen that appears when the Android app is launched.

To create a method channel in Android, we need access to the FlutterEngine. Override the configureFlutterEngine() method to gain this access.

Replace TODO 1 with the following code snippet:

val methodChannel = MethodChannel(
        flutterEngine.dartExecutor.binaryMessenger,
        channelName
    )

Here, we create an instance of MethodChannel, which requires a BinaryMessenger. We obtain an instance from the dart executor of the flutter engine. Then, we pass in a channelName, which must correspond to its Dart counterpart.

Next, set up a method call handler to listen and handle messages from Dart. Replace TODO 2 with the code snippet below:

methodChannel.setMethodCallHandler { call, result ->
    // Extract the argument as a map
    val args = call.arguments as Map<*, *>

    // The [when] keyword in Kotlin is similar to the [switch] keyword in Dart
    when (call.method) {
        // When method name matches "createFile" call the `createTextFile()` method passing in required arguments
        "createFile" -> {
            createTextFile(
                args["fileName"] as String,
                args["content"] as String,
                result
            )
        }
    }
}

Then, to return a success or error response, replace TODO 3 and TODO 4 with result.success(true) and result.error("0", e.message, e.cause) respectively.

iOS Implementation

The implementation of iOS is similar to the Android setup.

Navigate to /ios/Runner/AppDelegate.swift. The AppDelegate in iOS is the root object of an iOS application.

The first thing is to get access to FlutterViewController. Replace TODO1 with the code below:

let controller : FlutterViewController = window?.rootViewController as! FlutterViewController

Then, replace TODO2 with the following line of code:

let methodChannel = FlutterMethodChannel(name: channelName, binaryMessenger: controller.binaryMessenger)

Next, replace TODO3 with:

methodChannel.setMethodCallHandler({
      (call, result) -> () in

      // Check the call method, continue if "createFile" else send back an error
      guard call.method == "createFile" else {
          result(FlutterMethodNotImplemented)
          return
      }

      // Get method arguments
      let arguments = call.arguments as! NSDictionary

      // Call function to create a file.
      self.createTextFile(fileName: arguments["fileName"] as! String, fileContent: arguments["content"] as! String, result:result)
  })

Finally, replace TODO4 and TODO5 with result(true) and result(FlutterError(code: "0", message: "Saving file failed", details: nil)) respectively.

Conclusion

With the method channel setup for both platforms complete, you can now run both applications. You can view the complete method channel implementation by checking out the method-channel-implementation branch.

Frequently Asked Questions (FAQs) about Flutter and Platform Integration

1. What is Flutter’s role in platform integration?

  • Answer: Flutter serves as a versatile framework for developing cross-platform applications. It offers seamless integration with native platform components, enabling developers to access platform-specific features and functionalities.

2. What are platform channels in Flutter?

  • Answer: Platform channels in Flutter are mechanisms that facilitate communication between Flutter code and native platform code. They allow for bi-directional message passing, enabling Flutter applications to interact with platform-specific APIs and services.

3. What are some common scenarios where platform integration is necessary in Flutter applications?

  • Answer: Platform integration becomes necessary in Flutter applications when developers need to access platform-specific features such as geolocation, sensors, notifications, camera functionalities, and more. It allows applications to leverage the full capabilities of the underlying operating systems.

4. How does Flutter handle platform-specific implementations?

  • Answer: Flutter provides various approaches for handling platform-specific implementations. Developers can use platform channels, pigeon, and foreign function interface (FFI) to establish communication between Flutter and native components, allowing for seamless integration of platform-specific functionalities.

5. What is the purpose of method channels in Flutter?

  • Answer: Method channels in Flutter enable asynchronous communication between Flutter code and native platform code. They are typically used for one-time tasks and allow Flutter applications to invoke platform-specific methods and receive responses.

6. What platforms does Flutter support for application development?

  • Answer: Flutter supports a wide range of platforms for application development, including Android, iOS, Linux, Windows, and macOS. This allows developers to create cross-platform applications that run seamlessly across different operating systems.

7. How can developers get started with platform integration in Flutter?

  • Answer: Developers can get started with platform integration in Flutter by exploring official documentation, tutorials, and guides provided by the Flutter team. These resources offer comprehensive insights into using platform channels, pigeon, and FFI to communicate with native platform components.

Join Our Whatsapp Group

Join Telegram group

8. Are there any pre-built solutions or packages available for common platform integration tasks in Flutter?

  • Answer: Yes, Flutter ecosystem offers a wide range of pre-built plugins and packages for common platform integration tasks. Developers can leverage these solutions to streamline the development process and enhance the functionality of their applications without reinventing the wheel.

108 thoughts on “Working with native elements in Flutter: Platform Channel vs Pigeon vs Foreign Function Interface (FFI)”

Leave a Reply

Unlocking Potential with Apple Vision Pro Labs Navigating 2023’s Top Mobile App Development Platforms Flutter 3.16: Revolutionizing App Development 6 Popular iOS App Development Languages in 2023 Introducing Workflow Apps: Your Flutter App Development Partner