guricode

[Flutter] 네이티브 코드와 연동하는 방법 본문

앱/Flutter&Dart

[Flutter] 네이티브 코드와 연동하는 방법

agentrakugaki 2025. 10. 20. 09:30

 

1) 플랫폼 채널 (Platform Channels)

Flutter(Dart)와 Android(Kotlin)/iOS(Swift) 사이에 문자열 메서드 호출 + 직렬화된 메시지로 통신한다.
단발 호출은 MethodChannel, 스트림 이벤트는 EventChannel, raw 메시지는 BasicMessageChannel을 쓴다.

흐름

Dart가 invokeMethod("getBattery") → 네이티브가 같은 채널 이름으로 핸들러 등록 → 결과 반환.

예시: 배터리 잔량 조회 (MethodChannel)

Dart

import 'package:flutter/services.dart';

class NativeBridge {
  static const _ch = MethodChannel('app/native');

  static Future<int> getBatteryLevel() async {
    final level = await _ch.invokeMethod<int>('getBattery');
    return level ?? -1;
  }
}

Android (Kotlin)

class MainActivity : FlutterActivity() {
  override fun configureFlutterEngine(engine: FlutterEngine) {
    super.configureFlutterEngine(engine)
    MethodChannel(engine.dartExecutor.binaryMessenger, "app/native")
      .setMethodCallHandler { call, result ->
        if (call.method == "getBattery") {
          val level = getBatteryLevel() // 구현
          result.success(level)
        } else result.notImplemented()
      }
  }
}

iOS (Swift)

@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    let controller = window?.rootViewController as! FlutterViewController
    let ch = FlutterMethodChannel(name: "app/native", binaryMessenger: controller.binaryMessenger)

    ch.setMethodCallHandler { call, result in
      if call.method == "getBattery" {
        result(Int(UIDevice.current.batteryLevel * 100))
      } else { result(FlutterMethodNotImplemented) }
    }
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

2) 플러그인 (Plugin)

위 채널 코드를 재사용 가능한 패키지로 캡슐화한다.
flutter create -t plugin my_plugin으로 스캐폴드 생성. 퍼블리시 가능하고 모듈 경계가 명확해진다.


3) Pigeon (타입 안전 RPC 코드 생성)

문자열 키와 맵 직렬화의 취약점을 피하기 위해, Dart 인터페이스 정의 → Dart/Swift/Kotlin 스텁 자동 생성.
런타임 오타·직렬화 실수를 줄인다.

pigeon/dart_api.dart (예시 정의)

import 'package:pigeon/pigeon.dart';

class BatteryLevel {
  int? value;
}

@HostApi()
abstract class DeviceApi {
  BatteryLevel getBattery();
}

이 파일을 기준으로 Pigeon 실행 → 각 플랫폼 스텁 생성 → 네이티브에서 DeviceApi 구현, Dart에서는 타입 안전 호출.


4) FFI (Foreign Function Interface)

C/C++ 라이브러리를 Dart에서 직접 호출한다. JNI/Obj-C 브리지가 없고 성능이 좋다.
다만 메모리 안전과 포인터 수명 관리를 개발자가 책임진다.

예시: C 합계 함수 호출

C (sum.c)

#include <stdint.h>
int64_t sum_to(int64_t n){ int64_t s=0; for(int64_t i=0;i<n;i++) s+=i; return s; }

Dart (dart:ffi 바인딩)

import 'dart:ffi' as ffi;
import 'dart:io';

typedef c_sum_to = ffi.Int64 Function(ffi.Int64);
typedef d_sum_to = int Function(int);

final lib = ffi.DynamicLibrary.open(
  Platform.isAndroid ? 'libsum.so' : 'libsum.dylib'
);
final sumTo = lib.lookupFunction<c_sum_to, d_sum_to>('sum_to');

void main() {
  print(sumTo(1000000)); // 네이티브 C 실행
}

5) PlatformView (네이티브 UI 임베딩)

WebView, 지도 같은 네이티브 뷰 자체를 Flutter 위젯 트리에 삽입한다.
스크롤/오버레이/성능 이슈를 고려해야 한다.

예시: Android 네이티브 뷰 삽입

class NativeBanner extends StatelessWidget {
  const NativeBanner({super.key});
  @override
  Widget build(BuildContext context) {
    return AndroidView(
      viewType: 'native/banner',
      layoutDirection: TextDirection.ltr,
      creationParams: {'text': 'Hello from Android'},
      creationParamsCodec: const StandardMessageCodec(),
    );
  }
}

Android 쪽에서 PlatformViewFactory를 등록해 viewType: 'native/banner'를 생성한다.

 

 

 

Flutter 네이티브 연동의 기본값은 플랫폼 채널, 타입 안전이 필요하면 Pigeon, 고성능 네이티브 라이브러리는 FFI, 네이티브 UI가 필요하면 PlatformView를 쓴다.