플랫폼 채널 — MethodChannel로 네이티브 코드 호출하기

Flutter만으로 해결할 수 없는 기능(배터리 정보, 센서, 네이티브 SDK 등)은 플랫폼 채널을 통해 iOS/Android 네이티브 코드를 호출해야 합니다.


플랫폼 채널 종류

채널용도통신 방식
MethodChannel메서드 호출/응답요청-응답
EventChannel지속적인 데이터 스트림이벤트 스트림
BasicMessageChannel단순 메시지 교환메시지 전달

가장 자주 쓰는 것은 MethodChannel 입니다.


MethodChannel 기본

Flutter 측 (Dart)

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)

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)

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
// 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
// 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();
  },
)

지원되는 데이터 타입

DartKotlinSwift
nullnullnil
boolBooleanBool
intInt/LongInt
doubleDoubleDouble
StringStringString
ListListArray
MapHashMapDictionary

면접 포인트: 플랫폼 채널은 비동기 로 동작하며, 메인 스레드에서 호출됩니다. 네이티브 측에서 무거운 작업을 하면 UI가 버벅일 수 있으므로, 네이티브 측에서도 백그라운드 스레드를 사용해야 합니다.


정리

  • MethodChannel로 Flutter ↔ 네이티브 간 메서드 호출/응답이 가능합니다
  • EventChannel로 네이티브에서 Flutter로 지속적인 이벤트 스트림을 전달합니다
  • 채널 이름은 패키지명 형태로 고유하게 지정하세요
  • 대부분의 네이티브 기능은 이미 pub.dev에 패키지로 존재하니 먼저 확인하세요
  • 네이티브 측에서의 무거운 작업은 백그라운드 스레드에서 처리해야 합니다
댓글 로딩 중...