Flutter로 개발한 애플리케이션을 HarmonyOS 플랫폼으로 이전하려는 개발자들이 늘어나고 있습니다. 하지만 Flutter는 블루투스, 센서, 푸시 알림 등의 디바이스 네이티브 기능을 직접 제공하지 않기 때문에, 플랫폼 채널(Platform Channel)을 통해 HarmonyOS 네이티브 코드와 통신해야 합니다.
이 글에서는 Flutter for Harmony 프로젝트에서 HarmonyOS의 시스템 API를 호출하는 실제 방법을 단계별로 설명합니다. 예제로 디바이스의 하드웨어 정보를 가져오는 기능을 구현합니다.
개발 환경 구성
필수 도구
- DevEco Studio 4.0 이상 (HarmonyOS SDK 포함)
- HarmonyOS를 지원하는 Flutter SDK (커뮤니티 버전 또는 포크)
- 실제 기기 또는 에뮬레이터 (HarmonyOS 3.1+)
참고: Flutter 공식 버전은 아직 HarmonyOS를 정식 지원하지 않습니다. 본문에서는 안정적인 뮤니티 솔루션을 기준으로 설명합니다.
프로젝트 생성
DevEco Studio에서 "Flutter for HarmonyOS" 템플릿을 선택하거나 터미널에서 다음 명령을 실행합니다:
flutter create --platforms=harmony harmony_flutter_demo
생성된 프로젝트 구조는 다음과 같습니다:
harmony_flutter_demo/
├── flutter_module/ # Dart 소스 코드
├── entry/ # HarmonyOS 네이티브 모듈 (HAP)
│ └── src/main/ets/ # ArkTS 네이티브 코드
└── oh-package.json5 # 프로젝트 설정
Platform Channel 동작 방식
Flutter는 MethodChannel을 통해 Dart와 네이티브 플랫폼 간 방향 통신을 구현합니다:
| 측면 | 동작 |
|---|---|
| Dart | invokeMethod로 메서드 호출 |
| 네이티브 | 동일 이름의 채널 등록 및 핸들러 구현 |
HarmonyOS의 네이티브 언어는 ArkTS(TypeScript 기반)이며, 여기서 메서드 핸들러를 등록합니다.
실전: 하드웨어 정보 조회
Dart 측: MethodChannel 정의
lib/main.dart에 다음 코드를 추가합니다:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const HarmonyApp());
}
class HarmonyApp extends StatelessWidget {
const HarmonyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'HarmonyOS 연동',
home: const DeviceInfoPage(),
);
}
}
class DeviceInfoPage extends StatefulWidget {
const DeviceInfoPage({super.key});
@override
State<DeviceInfoPage> createState() => _DeviceInfoPageState();
}
class _DeviceInfoPageState extends State<DeviceInfoPage> {
static const _harmonyChannel = MethodChannel('tech.harmony.hardware');
String _hardwareInfo = '정보 없음';
Future<void> _fetchHardwareInfo() async {
String info;
try {
final String? response = await _harmonyChannel.invokeMethod('fetchDeviceSpec');
info = response ?? '조회 실패';
} on PlatformException catch (e) {
info = '오류 발생: ${e.message}';
}
if (!mounted) return;
setState(() => _hardwareInfo = info);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('하드웨어 정보')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_hardwareInfo, style: const TextStyle(fontSize: 16)),
const SizedBox(height: 24),
FilledButton(
onPressed: _fetchHardwareInfo,
child: const Text('정보 가져오기'),
),
],
),
),
);
}
}
핵심 포인트:
- 채널명:
'tech.harmony.hardware' - 호출 메서드:
'fetchDeviceSpec'
ArkTS 측: 채널 핸들러 등록
entry/src/main/ets/EntryAbility.ts을 수정합니다:
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import { BusinessError } from '@ohos.base';
// 커뮤니티 브리지 라이브러리 (예: @openharmony/flutter_bridge)
import { FlutterBridge } from '@openharmony/flutter_bridge';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage): void {
hilog.info(0x0000, 'EntryAbility', 'WindowStage 생성');
// Flutter 채널 등록
const bridge = new FlutterBridge('tech.harmony.hardware');
bridge.setHandler((request) => {
if (request.method === 'fetchDeviceSpec') {
return this._getDeviceSpecification();
}
return Promise.reject('METHOD_NOT_FOUND');
});
}
private async _getDeviceSpecification(): Promise<string> {
try {
const deviceInfo = require('@ohos.deviceInfo');
const spec = `${deviceInfo.brand} ${deviceInfo.model} (${deviceInfo.deviceType})`;
return spec;
} catch (err) {
const error = err as BusinessError;
hilog.error(0x0000, 'Hardware', '조회 실패: %{public}s', error.message);
throw new Error(`HARDWARE_QUERY_FAILED: ${error.message}`);
}
}
}
의존성 설정
oh-package.json5에 브리지 라이브러리를 추가합니다:
{
"dependencies": {
"@openharmony/flutter_bridge": "^0.2.1"
}
}
권한 선언
entry/src/main/module.json5에 필요한 권한을 추가합니다:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.GET_DEVICE_INFO",
"reason": "$string:device_info_reason"
}
]
}
}
문제 해결 가이드
| 증상 | 원인 및 해결 |
|---|---|
MissingPluginException | 채널명 불일치 확인, 네이티브 등록 여부 점검 |
| 콜백 미실행 | ArkTS 측 hilog 로그 추가로 등록 확인 |
| 권한 거부 | module.json5 권한 선언 및 사용자 승인 확인 |
| 빌드 오류 | Flutter 모듈과 HAP 모듈 연결 상태, build-profile.json5 검증 |
아키텍처 확장
복잡한 시나리오에서는 다음 구조를 고려하세요:
Dart Layer (UI/Logic)
↓ MethodChannel
ArkTS Bridge (Type Safety)
↓ NAPI / C++
Native System API (HarmonyOS)
이 구조는 성능이 중요한 작업(미디어 코딩, 그래픽 처리 등)에 유리합니다.