본문 바로가기
Flutter/Flutter 필수개념

초간단 Flutter Method Channel 배우기

by 붕어사랑 티스토리 2023. 9. 12.
반응형

https://docs.flutter.dev/platform-integration/platform-channels

 

Writing custom platform-specific code

Learn how to write custom platform-specific code in your app.

docs.flutter.dev

 

1. 메소드 채널이란?

 

플러터에서 native code(android, ios)를 동작하고 싶을 때 사용하는 인터페이스이다.

 

 

 

 

2. 어떻게 만드는데?

먼저 flutter 코드에 아래와 같이 methodChannel을 생성한다. 생성할 때 채널이름을 넘겨준다.

그리고 채널을 통해 네이티브 메소드를 호출한다. 이때 method호출은 async/await를 지원한다.

static const channel = MethodChannel('채널이름');
var result = await channel.invokeMethod('네이티브메소드 이름');

 

 

 

3. 네이티브 코드는?

 

Android

 

MainAcitivity(혹은 FlutterActivity를 상속받는놈)에 configureFlutterEngine()안에 메소드 채널을 setMethodCallHandler()을 이용하여 정의하면 된다.

import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
  private val CHANNEL_NAME = "채널이름"

  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    var channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME).setMethodCallHandler {
      call, result ->
      // 이때 메소드는 메인스레드에서 호출된다.
    }
  }
}

 

위 코드에서 람다함수의 인풋으로 call, result를 전달받는것을 볼 수 있다.

 

여기서 callmethodargument라는 두가지 프로퍼티를 가지고 있다.

 

Call

  • method : Flutter에서 호출한 함수의 이름정보이다
  • argument : Flutter에서 네이티브 함수를 호출 할 때

 

 

 

Result

result의 경우 success와 error그리고 notImplemented 메소드가 있다

  • success : 메소드의 수행이 성공할 때 호출해야하는 메소드. Object값으로 result를 올려준다
  • error : 메소드의 수행이 실패할 때 호출되는 메소드. string으로 errorCodeerrorMessage, Object로 errorDetails를 올려준다
  • notImplemented : 구현되지 않았을 때 호출해준다

 

 

위 정보를 바탕으로 메소드를 작성해보면 아래처럼 된다

MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
  // This method is invoked on the main thread.
  call, result ->
  if (call.method == "네이티브 메소드 이름") {
    val ret = 네이티브메소드()

    if (ret != -1) {
      result.success(ret)
    } else {
      result.error("UNAVAILABLE", "method not available.", null)
    }
  } else {
    result.notImplemented()
  }
}

 

 

 

iOS

ios도 안드로이드와 유사하다. 먼저 application 메소드를 override한다. 그다음 MethodChannel을 만든 후 메소드 핸들러를 달아준다.

 

@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: "채널이름",
                                              binaryMessenger: controller.binaryMessenger)
    channel.setMethodCallHandler({
      (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
      // This method is invoked on the UI thread.
      // Handle battery messages.
    })

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

 

채널의 구현부도 안드로이드와 동일하다. call을 이용해 플러터에서 호출한 메소드 이름을 확인한 뒤, 네이티브 메소드를 호출한다.

 

channel.setMethodCallHandler({
  [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
  // This method is invoked on the UI thread.
  guard call.method == "네이티브메소드" else {
    result(FlutterMethodNotImplemented)
    return
  }
  self?.네이티브메소드(result: result)
})

 

 

 

4. 백그라운드에서 실행하기

위 코드들은 한가지 문제가 있다. 전무 메인스레드에서 실행된다. 실력있는 개발자라면 백그라운드에서 실행시켜서 성능을 향상시켜야한다.

 

 

 

 

Flutter

플러터는 멀티 스레드를 지원하지 않으니 isolate로 메인스레드와 분리해서 실행시키자

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

void _isolateMain(RootIsolateToken rootIsolateToken) async {
  BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
  SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
  print(sharedPreferences.getBool('isDebug'));
}

void main() {
  RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
  Isolate.spawn(_isolateMain, rootIsolateToken);
}

 

 

Android / iOS

모바일에서는 flutter에서 task queue 라는 api를 제공한다. task queue를 넣어서 채널을 만들면 되는것이다.

 

Android

override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
  val taskQueue =
      flutterPluginBinding.binaryMessenger.makeBackgroundTaskQueue()
  channel = MethodChannel(flutterPluginBinding.binaryMessenger,
                          "com.example.foo",
                          StandardMethodCodec.INSTANCE,
                          taskQueue)
  channel.setMethodCallHandler(this)
}

iOS

public static func register(with registrar: FlutterPluginRegistrar) {
  let taskQueue = registrar.messenger.makeBackgroundTaskQueue()
  let channel = FlutterMethodChannel(name: "com.example.foo",
                                     binaryMessenger: registrar.messenger(),
                                     codec: FlutterStandardMethodCodec.sharedInstance,
                                     taskQueue: taskQueue)
  let instance = MyPlugin()
  registrar.addMethodCallDelegate(instance, channel: channel)
}

 

 

 

5. 네이티브에서 데이터를 stream으로 올려주려면?

이럴 땐 method channel이 아닌, event channel을 사용하면 된다.

 

하기 개념을 기억하자

 

  • 이벤트채널 : 메소드채널처럼, 이벤트가 발생하면 플러터에게 이벤트를 전달하는 채널이다
  • 이벤트핸들러 : 실질적으로 이벤트를 일으키는 클래스이다. onListenonCancel 메소드를 구현해야한다.

 

 

 

핸들러의 구현법은 다음과 같다

 

  • onListen에서 eventSink를 받아온다. 이 함수는 핸들러를 이벤트 채널에 붙일 때 호출된다
  • onCancle에서 eventSink를 null로 반납한다 
  • (optional) 싱글톤으로 만들면 개발하기에 편리함
  • Android의 경우 success, iOS의 경우 sink 함수로 플러터에 이벤트를 전달 할 수 있다.

Android

import android.os.Handler
import android.os.Looper
import io.flutter.plugin.common.EventChannel

class MyHandler: EventChannel.StreamHandler {
    private var handler = Handler(Looper.getMainLooper())
    private var eventSink: EventChannel.EventSink? = null

    companion object {
        private val instance = MyHandler()

        fun getInstance() = instance
    }

    override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
        eventSink = events
    }

    override fun onCancel(arguments: Any?) {
        eventSink = null
    }

    fun triggerEvent(result: String) {
        handler.post {
            eventSink?.success(result)
        }
    }
}

 

iOS

import Foundation

class MyHandler: NSObject, FlutterStreamHandler {
    static let shared = MyHandler()

    private var eventSink: FlutterEventSink?

    private override init() {}

    func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        eventSink = events
        return nil
    }

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

    func triggerEvent(result: String) {
        guard let sink = eventSink else { return }
        sink(result)
    }
}

 

 

다음으로 네이티브 코드에 이벤트채널을 생성한 뒤 앞서 만든 핸들러를 등록해준다.

 

Android (MainActivity.kt)

 val eventChannel = EventChannel(flutterEngine.dartExecutor.binaryMessenger, "MyEventChannel");
 eventChannel.setStreamHandler(MyHandler())

iOS (AppDelegate)

let eventChannel = FlutterEventChannel(name: "MyEventChannel", binaryMessenger: controller.binaryMessenger)
eventChannel.setStreamHandler(MyHandler())

 

 

 

마지막으로 Flutter에서 이벤트를 아래처럼 받으면 된다.

var eventChannel = EventChannel("MyEventChannel");
var eventStream = eventChannel.receiveBroadcastStream()
    ..listen((event) {
        
    	이벤트 처리 코드 작성
      
    });

 

반응형

댓글