Flutter platform channels

How to integrate platform-specific code (written in Swift, Kotlin, Java, or Objective-C) into a Flutter app using platform channels.

In my code, I have some examples: https://github.com/hanhmh1203/flutter-communicate-native

  1. Basic send and receive the message by MethodChannel to read some information of the device. (Android and IOS)

  2. Reading sensor's info of the device, used: EventChannel (Android and IOS)

  3. Tracking location when the app is not running (Android only)

  4. Start a Native view for scan barcode and handle result in Flutter code. (Android and IOS)

  5. Create a view (camera scan barcode) and include it a Widget Flutter. (Android only)

  1. Flutter is a cross-platform framework for building mobile apps with a single codebase.

  2. However, sometimes it's necessary to use platform-specific code to access features that aren't available in Flutter.

  3. Platform channels provide a way to communicate between Flutter code and platform-specific code.

  4. Platform channels support different data types, such as strings, integers, and maps.

  5. To use platform channels in Flutter, you need to define a method channel in your Dart code and a corresponding channel handler in your platform-specific code.

  6. The platform-specific code can send messages to the Dart code via the channel handler, and vice versa.

  7. You can use platform channels to perform tasks such as accessing device sensors, displaying native UI components, and handling push notifications.

The most basic way

  1. Setting up a MethodChannel:

  • Create a MethodChannel instance in your Flutter code:

    • final MethodChannel _methodChannel = const MethodChannel('basic_page');

  • Create a MethodChannel instance in your Android or iOS code:

    • val basicMethodChannel = MethodChannel(messenger, "basic_page")

  • In the Android code, set up a method call handler for the MethodChannel:

    • basicMethodChannel.setMethodCallHandler { methodCall: MethodCall, result: MethodChannel.Result -> ... }

  • In the iOS code, set up a method call handler for the MethodChannel:

    • basic.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in ... })

  1. Sending a method call with arguments:

  • In your Flutter code, call a platform-specific method and pass arguments:

    • _methodChannel.invokeMethod("getDeviceInfo", argument);

  • In the Android code, receive the method call and extract arguments:

    • val argument = methodCall.arguments as Int

  • In the iOS code, receive the method call and extract arguments:

    • let argument = call.arguments

  1. Returning a result:

  • In the Android and iOS code, use the Result or FlutterResult object to return a result to Flutter:

    • result.success("$version\n argument: ${argument}")

  • In your Flutter code, receive the result:

    • deviceInfo = await _methodChannel.invokeMethod("getDeviceInfo", argument);

  1. Handling errors:

  • Use try-catch blocks or error handling functions to handle errors that may occur in the method call or result handling process.

Flutter code
Android code: call in MainActivity: FlutterActivity()
Ios code in: AppDelegate: FlutterAppDelegate

Notice: MethodChannel name ("basic_pace") and methodCall name ("getDeviceInfo") must be the same.

  • By using MethodChannel, you can call platform-specific code and pass arguments and receive results in your Flutter app.

  • This allows you to access device-specific functionality and integrate with native APIs.

Stream in MethodChannel

  1. Setting up an EventChannel:

  • Create an EventChannel instance in your Flutter code:

    • final EventChannel _pressureEventChannel = const EventChannel('environment_sensors/pressure');

  • Create an EventChannel instance in your Android or iOS code:

    • val pressureEventChannel = EventChannel(messenger, "environment_sensors/pressure")

// Some code
  final EventChannel _pressureEventChannel =
  const EventChannel('environment_sensors/pressure');
  Stream<double>? _pressureEvents;
  
  void startReading() async {
      _pressureSubscription = pressure.listen((pressure) {
        pressureReading = pressure;
        update();
      });
    }
  }
  
  Stream<double> get pressure {
    _pressureEvents ??= _pressureEventChannel
        .receiveBroadcastStream()
        .map((event) => double.parse(event.toString()));
    return _pressureEvents!;
  }

Android code:

// Some code
  private var pressureChannel: EventChannel? = null
    private fun setupStreamForPressure(messenger: BinaryMessenger) {
        pressureChannel = EventChannel(messenger, ConstantEnvironmentSensor.pressureChannelName)
        val streamChannel = SensorStreamHandler(sensorManager, Sensor.TYPE_PRESSURE)
        pressureChannel!!.setStreamHandler(streamChannel)
    }
    
class SensorStreamHandler(
    private val sensorManager: SensorManager,
    sensorType: Int,
    private val interval: Int = SensorManager.SENSOR_DELAY_NORMAL
) : EventChannel.StreamHandler, SensorEventListener2 {
    private val sensor = sensorManager.getDefaultSensor(sensorType)
    private var eventSink: EventChannel.EventSink? =null
    override fun onSensorChanged(event: SensorEvent?) {
        val sensorValue = event!!.values[0]
        eventSink?.success(sensorValue)
    }
    override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
        if(sensor!=null){
            eventSink = events
            sensorManager.registerListener(this, sensor, interval)
        }
    }

in IOS:

// Some code
class AppDelegate:
    let pressureStream = PressureStreamHandler()
      let pressureChannelName = "environment_sensors/pressure"
      let pressureEventChannel = FlutterEventChannel(name: pressureChannelName, binaryMessenger: controller.binaryMessenger)
      pressureEventChannel.setStreamHandler(pressureStream)
      ///////
 
  class PressureStreamHandler : NSObject, FlutterStreamHandler{
    let altimeter = CMAltimeter()
    private let queue = OperationQueue()
    func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        if(CMAltimeter.isRelativeAltitudeAvailable()){
            altimeter.startRelativeAltitudeUpdates(to:  queue){(data, error) in if (data != nil){
                let pressurePascal = data?.pressure
                events(pressurePascal!.doubleValue * 10)
            }}
        }
        return nil
    }

  1. Receiving a stream:

  • In your Flutter code, use the EventChannel to receive a stream of data:

    • _pressureEvents = _pressureEventChannel.receiveBroadcastStream().map((event) => double.parse(event.toString()));

  • In the Android or iOS code, send data to the EventChannel:

    • eventSink.success(pressure);

  1. Starting and stopping a stream:

  • In your Flutter code, start listening to the stream to receive data:

    • _pressureSubscription = pressure.listen((pressure) { ... });

  • In your Flutter code, stop listening to the stream to stop receiving data:

    • _pressureSubscription?.cancel();

Use case to use platform channel:

  • Reading device information: Use platform channels to retrieve device information that is not available through the device_info package.

  • Running a service in Android to track location in real-time: Use platform channels to start and stop a service that tracks the device's location even when the app is not running.

  • Starting a native camera or barcode scanner view: Use platform channels to start a custom view for the camera or barcode scanner and handle its events and results in Flutter code.

  • Creating a platform view in Flutter: Use platform channels to send and receive messages to and from a native view embedded in your Flutter widget.

  • Accessing native APIs: Use platform channels to access native APIs that are not available through Flutter plugins.

  • Interacting with third-party libraries: Use platform channels to interact with third-party libraries that are not available through Flutter plugins. some libraries in native are better.

Last updated