소개
Flutter는 주로 Dart 언어를 사용하지만, 때때로 특정 기능을 위해 운영체제 별 네이티브 API를 사용해야 할 필요가 있습니다.
안드로이드는 JAVA 혹은 Kotlin, iOS는 Object-C나 Swift, Windows는 C++, macOS는 Objective-C, Linux는 C
이처럼 운영체제에 최적화된 언어로 작성된 소스를 네이티브 언어(Native Language)라고 합니다.
Flutter는 이러한 네이티브 언어로 작성된 코드와 통신할 수 있는 메커니즘으로 Platform Channel을 통하여 Message를 전달하는 방식을 제공합니다.
Platform Channel
개요
Platform Channel은 Dart(플러터) 코드와 네이티브 코드(예: Java, Kotlin, Swift) 간의 통신을 가능하게 하는 메커니즘입니다.
이를 통해 Flutter 앱에서 안드로이드나 iOS의 네이티브 기능을 사용할 수 있습니다.
예를 들어, 배터리 상태 확인, 카메라 기능 접근, 센서 데이터 사용 등이 이에 해당합니다.
구조
- Flutter App (Client): Flutter로 개발된 애플리케이션을 나타냅니다.
- MethodChannel: Flutter 앱과 네이티브 플랫폼 간의 통신을 위해 사용되는 채널입니다.
MethodChannel
을 통해 Flutter 앱은 메시지를 보내고, 네이티브 코드는 이 메시지를 수신하여 처리합니다. - FlutterMethodChannel: 이는 구체적으로 Flutter에서 MethodChannel을 사용할 때의 인스턴스를 나타냅니다. Flutter 코드에서 MethodChannel을 선언하고, 네이티브 코드와 통신할 때 사용합니다.
- iOS Host: iOS에서 Flutter 앱을 호스팅하는 부분입니다.
AppDelegate
와FlutterViewController
는 Flutter 앱을 iOS 애플리케이션 내에서 실행하는 역할을 합니다. 여기서 iOS 플랫폼 API나 제3자 API를 호출할 수 있습니다. - Android Host: 안드로이드에서 Flutter 앱을 호스팅하는 부분입니다.
Activity
는 안드로이드 애플리케이션의 진입점으로,FlutterView
를 통해 Flutter 앱이 표시됩니다. 안드로이드 플랫폼 API나 제3자 API를 호출할 수 있습니다.

작동 방식
- Flutter 앱에서
MethodChannel
을 통해 메시지를 보냅니다. - 해당 메시지는 iOS나 안드로이드 호스트로 전달됩니다.
- 호스트는 해당 메시지를 받아 네이티브 코드를 통해 처리합니다. 예를 들어, 배터리 상태를 확인하거나 카메라를 열 수 있습니다.
- 네이티브 처리 결과는 다시
MethodChannel
을 통해 Flutter 앱으로 전달됩니다. - Flutter 앱은 이 결과를 받아 사용자에게 보여주거나 다른 처리를 할 수 있습니다.
데이터 타입
개요
Flutter와 네이티브 사이에서 데이터를 주고받을 때는 서로 다른 언어 간의 데이터 타입 호환성을 고려해야 합니다.
예를 들어, Dart의 String
은 Java의 String
, iOS의 NSString
과 호환됩니다.
이러한 변환은 Flutter 시스템이 자동으로 처리해 줍니다.
언어(플랫폼) 별 데이터 타입
Dart | Java (Android) | Kotlin (Android) | Obj-C (iOS/macOS) | Swift (iOS) | C++ (Windows) | C (Linux) |
---|---|---|---|---|---|---|
null | null | null | NSNull | nil | std::nullptr_t | NULL |
bool | boolean | Boolean | BOOL | Bool | bool | bool |
int | int | Int | NSInteger | Int | int32_t | int32_t |
double | double | Double | double | Double | double | double |
String | String | String | NSString | String | std::string | const char* |
Uint8List | byte[] | ByteArray | FlutterStandardTypedData (binary) | FlutterStandardTypedData (binary) | std::vector<uint8_t> | uint8_t[] |
Int32List | int[] | IntArray | FlutterStandardTypedData (int32) | FlutterStandardTypedData (int32) | std::vector<int32_t> | int32_t[] |
Int64List | long[] | LongArray | FlutterStandardTypedData (int64) | FlutterStandardTypedData (int64) | std::vector<int64_t> | int64_t[] |
Float64List | double[] | DoubleArray | FlutterStandardTypedData (float64) | FlutterStandardTypedData (float64) | std::vector<double> | double[] |
List | List | List | NSArray | Array | std::vector<EncodableValue> | Array |
Map | Map | Map | NSDictionary | Dictionary | std::map<EncodableValue, EncodableValue> | Map |
예제
Platform Channel을 사용하여 핸드폰 정보를 받아오는 예제 구현해보겠습니다.
1. 플랫폼 별 Class 생성
iOS에서 사용될 CupertinoNativeApp
Class (CupertinoNativeApp
)을 정의하고
Android에서 사용될 AndroidNativeApp Class
(NativeApp)
을 표시하도록 합니다.
/lib/main/android_native_app.dart (AndroidNativeApp)
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class AndroidNativeApp extends StatefulWidget { const AndroidNativeApp({super.key}); @override State<StatefulWidget> createState() => _AndroidNativeApp(); } class _AndroidNativeApp extends State<AndroidNativeApp> { @override Widget build(BuildContext context) { return const Scaffold(); } }
/lib/main/cupertino_native_app.dart
class CupertinoNativeApp extends StatefulWidget { const CupertinoNativeApp({super.key}); @override State<StatefulWidget> createState() { return _CupertinoNative(); } } class _CupertinoNative extends State<CupertinoNativeApp> { @override Widget build(BuildContext context) { return const Scaffold(); } }
2. 플랫폼 확인
dart:io
를 활용하여 Platform을 확인하고, iOS일 때는 CupertinoNativeApp
을 표시하고
Android일 때는 NativeApp
을 표시하도록 합니다.
/lib/main.dart
import './main/android_native_app.dart'; import './main/cupertino_native_app.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'dart:io'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { if (Platform.isIOS) { return const CupertinoApp( home: CupertinoNativeApp(), ); } else { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const AndroidNativeApp(), ); } } }
3. Android에 message 전달
Message를 전달하기 위한 MethodChannel
을 이용하려면 serivces
패키지를 포함해야합니다.
import 'package:flutter/services.dart';
MethodChannel()
을 이용하기 위해 변수로 정의하고 (MethodChannel
내부 parameter는 어떤 통신을 할지 구분하는 키값)
휴대폰 정보를 받아오기 위한 변수도 정의합니다.
class _AndroidNativeApp extends State<AndroidNativeApp> { String _deviceInfo = ''; static const platformMsg = MethodChannel('com.flutter.dev/info');
이 변수를 이용하여, 휴대폰 정보를 가져오는 내부함수를 정의합니다.
Future<void> _getDeviceInfo() async { String batteryLevel; try { final String result = await platformMsg.invokeMethod('getDeviceInfo'); batteryLevel = 'Device info : $result'; } on PlatformException catch (e) { batteryLevel = "Failed to get Device info: '${e.message}'."; } setState(() { _deviceInfo = batteryLevel; }); }
4. Android Native API 호출
이제 메시지를 받아서 Android에서 처리하는 방법을 알아봅니다.
본인 프로젝트 이름은 android_api
입니다.
아래 파일에서 메시지를 처리할 수 있는 함수를 override 해줍니다.
android.os.Build
와 androidx.annotaion.NonNull
은 안드로이드에서 제공하는 기능을 사용하기 위함이고 나머지는 flutter 의 기능을 쓰기 위함입니다.
앞서 사용한 MethodChannel의 Key 값(com.flutter.dev/info
)을 초기화하, configureFlutterEngine()
을 override하여 MethodChannel로 들어오는 요청의 응답코드를 작성합니다.
이제 ‘getDeviceInfo’ 메시지를 처리할 함수를 정의한다.
앞서 안드로이드에서 추가한 Build를 사용하여 DEVICE
, BRAND
, MODEL
을 이용하여 디바이스 정보를 string으로 변환하여 반환합니다.
app/src/main/kotlin/com.example.android_api_1/MainActivity.kt
package com.example.android_api import android.os.Build import androidx.annotation.NonNull 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.flutter.dev/info" override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if(call.method == "getDeviceInfo") { val deviceInfo = getDeviceInfo() result.success(deviceInfo) } } } private fun getDeviceInfo() : String { val sb = StringBuffer() sb.append(Build.DEVICE+"\n") sb.append(Build.BRAND+"\n") sb.append(Build.MODEL+"\n") return sb.toString() } }
5. 반환 결과 표시
Android에서 표시할 AndroidNativeApp
에 getDeviceInfo를 통해 받아온 String을 표시합니다.
/lib/main/android_native_app.dart
class _AndroidNativeApp extends State<AndroidNativeApp> { String _deviceInfo = 'Unknown Info'; static const platformMsg = MethodChannel('com.flutter.dev/info'); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Natvie API Example'), ), body: Container( child: Center( child: Text( _deviceInfo, style: const TextStyle(fontSize: 30), ), ), ), floatingActionButton: FloatingActionButton( onPressed: () { _getDeviceInfo(); }, child: const Icon(Icons.get_app), ), ); } Future<void> _getDeviceInfo() async { String batteryLevel; try { final String result = await platformMsg.invokeMethod('getDeviceInfo'); batteryLevel = 'Device info : $result'; } on PlatformException catch (e) { batteryLevel = "Failed to get Device info: '${e.message}'."; } setState(() { _deviceInfo = batteryLevel; }); } }
결과
