요즘 센트리에서 발생한 에러를 추적하면서 네이티브 기기에대해 이해할 필요하다는 생각이 들었습니다. 그러던 중 플러터에서 네이티브 기기들의 속성을 가져다쓸때 사용하는 Method Channel에 대해 궁금증이 생겨 이번 주제로 정해 보았습니다.
잘못된 부분과 보충할 부분을 공유 해주신다면 정말정말 감사하겠습니다!
- 언제 사용할까?
앱을 만들다보면 notification, app lifecycle, sensor, camera, battery, sound, connectivity, device information, geolocation 등 플랫폼에 특화된 API의 사용이 필요할 때가 있습니다.
플러터로 플랫폼 코드의 사용을 도와주는 패키지들이 많이 있지만, 마땅한 패키지가 없을때 직접 플랫폼별 코드를 작성할 필요를 느끼게 됩니다. 이때 우리는 플랫폼의 정보를 직접 가져오기 위한 방법이 필요하게 됩니다.
플러터가 네이티브의 OS정보를 가져올 수 있는 방법은 3가지가 있습니다.
- Method Channel : 단발적, 표준 메세지 코덱을 사용합니다
- Event Channel : EventChannel은 stream 데이터가 필요할때 사용합니다. 결과로 플러터쪽에서 stream을 갖게되고, 플랫폼사이드에서 특정 이벤트가 일어날때마다 데이터를 받을 수 있게 됩니다.
- Basic Message Channel: Method Channel보다 더 기본적. 코덱을 커스텀하기위해 사용하는 방법입니다
이 세가지중 먼저 Method Channel의 사용법을 살펴보겠습니다.
- Method Channel이란?
Method Channel을 짧게 요약한다면 플러터앱과 플랫폼간의 양방향 창구를 열어서, 플러터 코드로 각 플랫폼(IOS, AOS)의 정보를 가져오거나 플랫폼에 특화된 어떤 작업을 실행할 수 있게 해주는 방법이라고 할 수 있습니다. 양방향 창구이기때문에 플랫폼에서 플러터 방향으로도 보낼 수도 있습니다. 그리고 이런 작업은 비동기로 실행됩니다.
Method Channel은 플러터와 각 플랫폼이 정보를 주고받는 창구이기때문에, 플러터와 플랫폼 코드에서 모두 채널을 열어줘야 합니다.
이때, 사용하는 채널명은 앱 내에서 유니크 해야하기 때문에 prefix로 플러그인 이름을 넣는것이 권장됩니다.
이 유니크한 채널명은 연결되는 플러터와 플랫폼 채널간에는 서로는 반드시 일치해야합니다.
예를들어 플러터에서 a 로 채널을 열었다면 안드로이드에서도 a 로 채널을 열어줘야 한다는 것입니다.
- 지원되는 플랫폼
플러터는 6개의 플랫폼을 지원합니다. 이중 웹을 제외한 5개의 플랫폼에서 platform channel을 사용할 수 있습니다.
플랫폼 | 언어 |
iOS | Swift or Objective-C |
Android | Kotlin or Java |
macOS | Swift or Objective-C |
Windows | C++ |
Linux | C |
웹에서는 플러터를 사용할때 Dart코드가 JavaScript코드로 변환되는데, package:js를 가져와서 바로 상호운용 할 수 있도록 지원되기 때문에 platform channel을 사용하지 않고도 JavaScript코드를 실행시킬 수 있다고 합니다.
플러터 프로젝트를 사용하면 Android:Kotlin, IOS:Swift 코드를 디폴트로 사용하기 때문에,
Java나 Objective-C를 사용하고싶다면 프로젝트만들 때 어떤 네이티브 언어를 사용할지 명시해줘야 합니다.
- 데이터 코덱
Method Channel은 데이터를 주고 받을때 간단한 json 형태의 효율적인 바이너리 직렬화를 지원하는 boolean, numbers, Strings, byte butters, List, Map등의 표준 메시지 코덱을 사용합니다 (참고: StandardMessageCodec). 이 값들에 대한 메시지 직렬화와 역직렬화는 값을 보내고 받을 때 자동으로 이루어집니다. 주고받은 메세지를 디코드 할때 아래의 상응되는 데이터 타입을 사용하게 된다고 합니다.
Dart | Andrlid | Ios |
null | null | nil (NSNull when nested) |
bool | java.lang.Boolean | NSNumber numberWithBool: |
int | java.lang.Integer | NSNumber numberWithInt: |
int, if 32 bits not enough | java.lang.Long | NSNumber numberWithLong: |
double | java.lang.Double | NSNumber numberWithDouble: |
String | java.lang.String | NSString |
Uint8List | byte[] | FlutterStandardTypedData typedDataWithBytes: |
Int32List | int[] | FlutterStandardTypedData typedDataWithInt32: |
Int64List | long[] | FlutterStandardTypedData typedDataWithInt64: |
Float64List | double[] | FlutterStandardTypedData typedDataWithFloat64: |
List | java.util.ArrayList | NSArray |
Map | java.util.HashMap | NSDictionary |
- Method Channel 을 사용하는 방법
1. 플러터 코드에서 MethodChannel을 선언합니다. 이때, 사용하는 채널명은 하나의 앱안에서 유일무이 해야합니다. 때문에 prefix로 플러그인 이름을 넣는 방법이 추천됩니다.
2. invokeMethod 파라미터로 네이티브코드에서 실행시킬 매서드 이름을 명시해줍니다.
Method Channel을 사용한 플랫폼과 플러터 코드간의 소통은 앞에서도 언급했듯이 async방식으로 진행됩니다.
*호출은 실패할 수도 있기 때문에 호출을 try-catch 문으로 감싸주세요.
static getCurrentScheduleExactAlarmState() async {
const platform = MethodChannel('fiet/permission/schedule_exact_alarm');//채널선언
bool? isPermitted;
try {
isPermitted = await platform.invokeMethod('getExactAlarmState');//네이티브에서 실행시킬 메서드이름 명시
} catch (e) {
FietLogger.error(
'Error occur on getCurrentScheduleExactAlarmState',
exception: e,
);
}
return isPermitted;
}
3. 이제 플러터코드가 아닌 안드로이드, IOS 네이티브 코드를 작성할 때 입니다. 먼저 네이티브에서도 채널을 열어줍니다.
채널 메서드(안드로이드: setMethodCallHandler, IOS: applicationDidFinishLaunchingWithOptions) 안에 핸들러를 추가해준다
- 안드로이드 & kotlin사용시: android > src > kotlin > MainActivity.kt 파일에 코드 작성한다
*안드로이드 스튜디오에서 작업하면 편합니다.
onCreate 메서드 안에서 MethodChannel을 만들고 setMethodCallHandler를 호출하세요.
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
"fiet/permission/schedule_exact_alarm"
).setMethodCallHandler { call, result ->
when (call.method) {
"getExactAlarmState" -> {
var resultState = getScheduleExactAlarmGrantedState(applicationContext)
result.success(resultState)
}
"requestExactAlarmStatePermission" -> {
var newlyRequestedState =
requestScheduleExactAlarmPermission(applicationContext)
result.success(newlyRequestedState)
}
else -> result.notImplemented()
}
}
- IOS & Swift 사용시: ios > Runner > AppDelegate.swift 파일에 코드를 작성한다.
다음으로, application:didFinishLaunchingWithOptions: 함수를 오버라이드 하고 'fiet/permission/schedule_exact_alarm' 를 채널 이름으로 사용하는 FlutterMethodChannerl을 생성해야 합니다.
@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: "fiet/permission/schedule_exact_alarm",
binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
switch call.method {
case "getExactAlarmState":
//메서드 실행
case "requestExactAlarmStatePermission":
//메서드 실행
default:
result(FlutterMethodNotImplemented)
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
- 정리
사용예시를 길게 적었지만 방식은 초반에 정리했듯이 간단합니다.
1. 플러터와 플랫폼에서같은 채널명을 사용하여 채널을 열어주고
2. 플러터에서 부른 메소드 이름을 플랫폼 코드에서 동일사용해서 작성해준다는것
이 두가지만 기억하면 플러터와 플랫폼을 연결하는 Method Channel을 사용할 수 있습니다.
개념을 이해하는데에는 오래 걸렸는데 정리하고 보니 간단한 내용이었습니다.
다만 어려운 점은 채널연결을 위해 결국 네이티브 코드를 작성 할 수 있어야 한다는 것입니다. 아무리 플러터가 한가지 언어로 두 플랫폼의 개발을 지원한다고 하지만 점점 네이티브 코드를 공부해야겠다는 생각이 많이 드네요.
읽어주셔서 감사합니다
- 참고한 사이트들
https://flutter-ko.dev/docs/development/platform-integration/platform-channels#플랫폼-채널-지원-데이터형과-코덱
https://papabee.tistory.com/m/284
https://blog.logrocket.com/using-flutters-methodchannel-invoke-kotlin-code-android/
'Weekly Flutter' 카테고리의 다른 글
[Flutter 주석 파헤치기] 02. Inherited Widget (0) | 2023.02.22 |
---|---|
[Flutter 아는 척하기] 플러터 선택의 이유 (0) | 2023.02.15 |
[Flutter Widget] size를 줄수 있는 위젯과 size를 줄 수 없는 위젯 (0) | 2023.02.05 |
[Flutter 주석 파헤치기] 01. StatefulWidget과 State (0) | 2023.01.31 |
[Flutter Forward] 레코드에 대해 알아보자 (0) | 2023.01.30 |