Zakria Khan
Zakria Khan

Reputation: 2203

How to stream data from swift to flutter using event channel?

I am making an app in which I stream data from native android and iOS side to flutter side and there I display the data in the UI.

I already did the android part. The android part is sending the data to flutter side and displays them in UI. But the problem is how to achieve same for iOS swift side.

Android code that works for me:

new EventChannel(getFlutterView(), "Eventchannelname").setStreamHandler(
            new EventChannel.StreamHandler() {
                @Override
                public void onListen(Object args, EventChannel.EventSink events) {
                    Log.w(TAG, "adding listener");
                    mEventSink = events; // I use mEventsink.success(data) to pass the data to flutter side

                @Override
                public void onCancel(Object args) {
                    Log.w(TAG, "cancelling listener");
                }
            }
    );

How can I achieve the same in Swift native code. I googled it and did not find anything that can help me.

I want same in swift as what I did in android java: I want to capture the events in local variable and then use that where I need to send data to flutter.

Upvotes: 10

Views: 15633

Answers (2)

ch271828n
ch271828n

Reputation: 17643

I also find an example in the official Flutter repo: https://github.com/flutter/flutter/blob/master/examples/platform_channel_swift

The code in Swift looks like the following:

import UIKit
import Flutter

enum ChannelName {
  static let battery = "samples.flutter.io/battery"
  static let charging = "samples.flutter.io/charging"
}

enum BatteryState {
  static let charging = "charging"
  static let discharging = "discharging"
}

enum MyFlutterErrorCode {
  static let unavailable = "UNAVAILABLE"
}

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, FlutterStreamHandler {
  private var eventSink: FlutterEventSink?

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    guard let controller = window?.rootViewController as? FlutterViewController else {
      fatalError("rootViewController is not type FlutterViewController")
    }
    let batteryChannel = FlutterMethodChannel(name: ChannelName.battery,
                                              binaryMessenger: controller.binaryMessenger)
    batteryChannel.setMethodCallHandler({
      [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
      guard call.method == "getBatteryLevel" else {
        result(FlutterMethodNotImplemented)
        return
      }
      self?.receiveBatteryLevel(result: result)
    })

    let chargingChannel = FlutterEventChannel(name: ChannelName.charging,
                                              binaryMessenger: controller.binaryMessenger)
    chargingChannel.setStreamHandler(self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  private func receiveBatteryLevel(result: FlutterResult) {
    let device = UIDevice.current
    device.isBatteryMonitoringEnabled = true
    guard device.batteryState != .unknown  else {
      result(FlutterError(code: MyFlutterErrorCode.unavailable,
                          message: "Battery info unavailable",
                          details: nil))
      return
    }
    result(Int(device.batteryLevel * 100))
  }

  public func onListen(withArguments arguments: Any?,
                       eventSink: @escaping FlutterEventSink) -> FlutterError? {
    self.eventSink = eventSink
    UIDevice.current.isBatteryMonitoringEnabled = true
    sendBatteryStateEvent()
    NotificationCenter.default.addObserver(
      self,
      selector: #selector(AppDelegate.onBatteryStateDidChange),
      name: UIDevice.batteryStateDidChangeNotification,
      object: nil)
    return nil
  }

  @objc private func onBatteryStateDidChange(notification: NSNotification) {
    sendBatteryStateEvent()
  }

  private func sendBatteryStateEvent() {
    guard let eventSink = eventSink else {
      return
    }

    switch UIDevice.current.batteryState {
    case .full:
      eventSink(BatteryState.charging)
    case .charging:
      eventSink(BatteryState.charging)
    case .unplugged:
      eventSink(BatteryState.discharging)
    default:
      eventSink(FlutterError(code: MyFlutterErrorCode.unavailable,
                             message: "Charging status unavailable",
                             details: nil))
    }
  }

  public func onCancel(withArguments arguments: Any?) -> FlutterError? {
    NotificationCenter.default.removeObserver(self)
    eventSink = nil
    return nil
  }
}

Upvotes: 5

Alexei Volkov
Alexei Volkov

Reputation: 859

just call mEventSink as a function

mEventSink(data)

Use FlutterEndOfEventStream constant to signal end of stream

mEventSink(FlutterEndOfEventStream)

if you going to send error to flutter side use

mEventSink(FlutterError(code: "ERROR_CODE",
                             message: "Detailed message",
                             details: nil))

Reference to API DOC

Complete swift example

let eventChannel = FlutterEventChannel(name: "your.channel.id", binaryMessenger: messenger!)                                                                            
        eventChannel.setStreamHandler(SwiftStreamHandler())      

.... 

class SwiftStreamHandler: NSObject, FlutterStreamHandler {                                                                                                                                  
    public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {                                                                     
        events(true) // any generic type or more compex dictionary of [String:Any]
        events(FlutterError(code: "ERROR_CODE",
                             message: "Detailed message",
                             details: nil)) // in case of errors
        events(FlutterEndOfEventStream) // when stream is over
        return nil                                                                                                                                                                           
    }                                                                                                                                                                                        

    public func onCancel(withArguments arguments: Any?) -> FlutterError? {                                                                                                                   
        return nil                                                                                                                                                                           
    }                                                                                                                                                                                        
}               

Upvotes: 16

Related Questions