Flutter와 웹앱 간 양방향 통신 구현
Vue/React/Angular 웹앱
↓ (JavaScript)
FlutterBridge.request()
↓
JavaScript Channel
↓
Flutter 앱
↓
Native 기능
↓
응답 반환
↓
Promise resolve
↓
웹앱 수신
// JavaScript에서 Flutter로 메시지 전송
const bridge = new FlutterBridge();
// 위치 요청
const location = await bridge.getLocation();
console.log(location); // { lat: 37.123, lng: 127.456 }
// 1. 요청 ID 생성
const requestId = generateId();
// 2. Promise 생성 및 저장
const promise = new Promise((resolve, reject) => {
pendingRequests[requestId] = { resolve, reject };
});
// 3. Flutter로 메시지 전송
window.FlutterBridge.postMessage(JSON.stringify({
action: 'GET_LOCATION',
payload: { requestId }
}));
// 4. Promise 반환 (응답 대기)
return promise;
// Flutter에서 웹앱으로 응답
await _controller.runJavaScript('''
if (window.FlutterBridge && window.FlutterBridge.handleResponse) {
window.FlutterBridge.handleResponse(
'$requestId',
'SUCCESS',
${jsonEncode(responseData)}
);
}
''');
// Flutter에서 웹앱으로 이벤트 전송 (요청 없이)
await _controller.runJavaScript('''
window.dispatchEvent(new CustomEvent('flutter-event', {
detail: { type: 'location-updated', data: $locationJson }
}));
''');
async request(action, payload = {}) {
const requestId = this.generateId();
return new Promise((resolve, reject) => {
// 타임아웃 설정
const timeout = setTimeout(() => {
delete this.pendingRequests[requestId];
reject(new Error('Request timeout'));
}, this.timeout);
// 요청 저장
this.pendingRequests[requestId] = {
resolve,
reject,
timeout
};
// Flutter로 전송
this.sendToFlutter(action, { ...payload, requestId });
});
}
Future _handleBridgeMessage(String rawJson) async {
final data = jsonDecode(rawJson);
final action = data['action'];
final payload = data['payload'] ?? {};
final requestId = payload['requestId'] ?? '';
switch (action) {
case 'GET_LOCATION':
await _getLocation(requestId);
break;
// 기타 액션들...
}
}
Future _sendResponse(
String requestId,
String status,
Map data
) async {
await _controller.runJavaScript('''
if (window.FlutterBridge) {
window.FlutterBridge.handleResponse(
'$requestId',
'$status',
${jsonEncode(data)}
);
}
''');
}
handleResponse(requestId, status, data) {
const request = this.pendingRequests[requestId];
if (!request) return;
clearTimeout(request.timeout);
delete this.pendingRequests[requestId];
if (status === 'SUCCESS') {
request.resolve(data);
} else {
request.reject(new Error(data.message));
}
}
try {
const location = await bridge.getLocation();
} catch (error) {
if (error.message === 'Request timeout') {
console.log('요청 시간 초과');
}
}
try {
const location = await bridge.getLocation();
} catch (error) {
if (error.message.includes('permission')) {
console.log('위치 권한이 필요합니다');
}
}
<script setup>
import { ref } from 'vue';
import FlutterBridge from './bridge';
const bridge = new FlutterBridge();
const location = ref(null);
const error = ref(null);
async function getLocation() {
try {
location.value = await bridge.getLocation();
} catch (err) {
error.value = err.message;
}
}
</script>
import { useState } from 'react';
import FlutterBridge from './bridge';
const bridge = new FlutterBridge();
function App() {
const [location, setLocation] = useState(null);
const getLocation = async () => {
try {
const loc = await bridge.getLocation();
setLocation(loc);
} catch (error) {
console.error(error);
}
};
return <button onClick={getLocation}>위치</button>;
}
{
"action": "GET_LOCATION",
"payload": {
"requestId": "req_1234567890",
// 기타 파라미터
}
}
{
"requestId": "req_1234567890",
"status": "SUCCESS",
"data": {
"lat": 37.123,
"lng": 127.456,
"accuracy": 10
}
}