본문 바로가기
Flutter

Flutter에서 Native 코드와 통신하기

by 안될개발 2025. 2. 1.

Flutter에서 Native 코드와 통신하기

Flutter에서 Native 코드와 통신하기

Flutter는 Android와 iOS 네이티브 코드와의 통신을 지원하여 플랫폼별 기능을 활용할 수 있습니다. 이를 통해 카메라, 위치 서비스, 센서 데이터 등 Flutter에서 직접 제공하지 않는 네이티브 기능을 사용할 수 있습니다. 이번 글에서는 Flutter에서 네이티브 코드와 통신하는 방법과 주요 기법을 소개합니다.


1. 플랫폼 채널 (Platform Channel) 소개

Flutter와 네이티브 코드(Android: Kotlin/Java, iOS: Swift/Objective-C) 간 데이터와 명령을 주고받기 위해 **플랫폼 채널 (Platform Channel)**을 사용합니다.

플랫폼 채널 흐름

  1. Flutter 코드에서 MethodChannel을 통해 네이티브 코드 호출
  2. 네이티브 코드에서 명령 처리 후 결과 반환
  3. Flutter 코드가 결과 수신 후 UI 업데이트

2. 플랫폼 채널 기본 구현

Flutter 코드 작성

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

class NativeCommunicationExample extends StatefulWidget {
  @override
  _NativeCommunicationExampleState createState() => _NativeCommunicationExampleState();
}

class _NativeCommunicationExampleState extends State<NativeCommunicationExample> {
  static const platform = MethodChannel('com.example/native');
  String _batteryLevel = 'Unknown battery level';

  Future<void> _getBatteryLevel() async {
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      setState(() {
        _batteryLevel = 'Battery level: $result%';
      });
    } on PlatformException catch (e) {
      setState(() {
        _batteryLevel = "Failed to get battery level: '${e.message}'";
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Native Communication Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(_batteryLevel),
            ElevatedButton(
              onPressed: _getBatteryLevel,
              child: Text('Get Battery Level'),
            ),
          ],
        ),
      ),
    );
  }
}

Android 코드 작성 (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/native"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
            call, result ->
            if (call.method == "getBatteryLevel") {
                val batteryLevel = getBatteryLevel()

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

    private fun getBatteryLevel(): Int {
        val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
        return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
    }
}

iOS 코드 작성 (Swift)

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/native",
                                              binaryMessenger: controller.binaryMessenger)

    batteryChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
      if call.method == "getBatteryLevel" {
        self.receiveBatteryLevel(result: result)
      } else {
        result(FlutterMethodNotImplemented)
      }
    }

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  private func receiveBatteryLevel(result: FlutterResult) {
    let device = UIDevice.current
    device.isBatteryMonitoringEnabled = true
    if device.batteryState == .unknown {
      result(FlutterError(code: "UNAVAILABLE",
                          message: "Battery level not available.",
                          details: nil))
    } else {
      result(Int(device.batteryLevel * 100))
    }
  }
}

3. 에러 처리 및 디버깅

플랫폼 코드 오류 처리

  • Android: result.error("CODE", "MESSAGE", null)으로 오류 반환
  • iOS: FlutterError 객체로 오류 반환

디버깅 팁

  • adb logcat 명령어로 Android 로그 확인
  • Xcode Debugger로 iOS 로그 확인

4. 플러그인 개발 시 고려 사항

  1. 플랫폼 별 코드 관리:
    네이티브 코드가 복잡해질 경우 별도의 파일로 분리하여 유지보수성을 높입니다.
  2. 비동기 처리:
    네이티브 작업이 오래 걸리는 경우 반드시 비동기 처리를 사용해 UI 블록을 방지합니다.
  3. 테스트:
    Flutter 쪽 테스트뿐만 아니라 네이티브 코드에 대한 단위 테스트도 작성합니다.

5. 결론

Flutter에서 네이티브 코드와 통신하면 플랫폼 고유의 기능을 활용하여 더 강력한 앱을 개발할 수 있습니다. 이번 글에서 소개한 플랫폼 채널 기법을 활용해 다양한 네이티브 기능을 프로젝트에 통합해 보세요.