플랫폼 채널 — MethodChannel로 네이티브 코드 호출하기
플랫폼 채널 — MethodChannel로 네이티브 코드 호출하기
Flutter만으로 해결할 수 없는 기능(배터리 정보, 센서, 네이티브 SDK 등)은 플랫폼 채널을 통해 iOS/Android 네이티브 코드를 호출해야 합니다.
플랫폼 채널 종류
| 채널 | 용도 | 통신 방식 |
|---|---|---|
| MethodChannel | 메서드 호출/응답 | 요청-응답 |
| EventChannel | 지속적인 데이터 스트림 | 이벤트 스트림 |
| BasicMessageChannel | 단순 메시지 교환 | 메시지 전달 |
가장 자주 쓰는 것은 MethodChannel 입니다.
MethodChannel 기본
Flutter 측 (Dart)
import 'package:flutter/services.dart';
class BatteryService {
// 채널 이름은 고유해야 함 (패키지명 형태 권장)
static const _channel = MethodChannel('com.example.app/battery');
Future<int> getBatteryLevel() async {
try {
final int result = await _channel.invokeMethod('getBatteryLevel');
return result;
} on PlatformException catch (e) {
throw Exception('배터리 정보를 가져올 수 없습니다: ${e.message}');
}
}
Future<bool> isCharging() async {
try {
final bool result = await _channel.invokeMethod('isCharging');
return result;
} on PlatformException catch (e) {
throw Exception('충전 상태를 확인할 수 없습니다: ${e.message}');
}
}
}
Android 측 (Kotlin)
// android/app/src/main/kotlin/.../MainActivity.kt
package com.example.app
import android.os.BatteryManager
import android.content.Context
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.example.app/battery"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"getBatteryLevel" -> {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "배터리 정보 없음", null)
}
}
"isCharging" -> {
result.success(isCharging())
}
else -> result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryManager = getSystemService(Context.BATTERY_SERVICE)
as BatteryManager
return batteryManager.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
)
}
private fun isCharging(): Boolean {
val batteryManager = getSystemService(Context.BATTERY_SERVICE)
as BatteryManager
return batteryManager.isCharging
}
}
iOS 측 (Swift)
// ios/Runner/AppDelegate.swift
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(
name: "com.example.app/battery",
binaryMessenger: controller.binaryMessenger
)
batteryChannel.setMethodCallHandler { (call, result) in
switch call.method {
case "getBatteryLevel":
UIDevice.current.isBatteryMonitoringEnabled = true
let batteryLevel = Int(UIDevice.current.batteryLevel * 100)
result(batteryLevel)
case "isCharging":
UIDevice.current.isBatteryMonitoringEnabled = true
let state = UIDevice.current.batteryState
result(state == .charging || state == .full)
default:
result(FlutterMethodNotImplemented)
}
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
인자 전달
// Dart에서 인자 전달
final result = await _channel.invokeMethod('greet', {
'name': '심정훈',
'age': 25,
});
// Kotlin에서 인자 받기
val name = call.argument<String>("name")
val age = call.argument<Int>("age")
result.success("안녕하세요, $name님!")
// Swift에서 인자 받기
if let args = call.arguments as? [String: Any],
let name = args["name"] as? String {
result("안녕하세요, \(name)님!")
}
EventChannel — 지속적인 이벤트 스트림
// Dart
class SensorService {
static const _eventChannel = EventChannel('com.example.app/sensor');
Stream<double> get accelerometerStream {
return _eventChannel.receiveBroadcastStream().map((event) {
return event as double;
});
}
}
// UI에서 사용
StreamBuilder<double>(
stream: SensorService().accelerometerStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('가속도: ${snapshot.data}');
}
return const CircularProgressIndicator();
},
)
지원되는 데이터 타입
| Dart | Kotlin | Swift |
|---|---|---|
| null | null | nil |
| bool | Boolean | Bool |
| int | Int/Long | Int |
| double | Double | Double |
| String | String | String |
| List | List | Array |
| Map | HashMap | Dictionary |
면접 포인트: 플랫폼 채널은 비동기 로 동작하며, 메인 스레드에서 호출됩니다. 네이티브 측에서 무거운 작업을 하면 UI가 버벅일 수 있으므로, 네이티브 측에서도 백그라운드 스레드를 사용해야 합니다.
정리
- MethodChannel로 Flutter ↔ 네이티브 간 메서드 호출/응답이 가능합니다
- EventChannel로 네이티브에서 Flutter로 지속적인 이벤트 스트림을 전달합니다
- 채널 이름은 패키지명 형태로 고유하게 지정하세요
- 대부분의 네이티브 기능은 이미 pub.dev에 패키지로 존재하니 먼저 확인하세요
- 네이티브 측에서의 무거운 작업은 백그라운드 스레드에서 처리해야 합니다
댓글 로딩 중...