티스토리 뷰
Webview 양방향 통신 비동기 -> 동기 전환 (w. Flutter)
webview_flutter
라이브러리를 사용하면서 Flutter를 이용하여 Device의 위치 기능, 정보 등 다양한 기능을 사용하기 위해 webview_flutter
의 내장 함수 ..addJavaScriptChannel()
로 post Message를 보내고 .runJavascript()
를 통해 웹의 JavaScript를 동작하여 양방향 통신 하였습니다.
그러나 다음과 같은 문제가 발생하게 되었습니다.
.runJavascript()
동작이 안드로이드에서는 되고 iOS에서는 되지 않는 문제
- 비동기적으로 실행되며
.runJavascript()
가 Flutter에서 실행되어 웹이 수시로 상태가 변화하지 못하는 문제
그래서 1번과 같은 문제로 인해 1번 문제 해결 방안에 대해서 찾아보고 질문도 남겼지만 정확한 답변을 얻지 못해서 아쉬워서 새로운 방안을 찾은 거였지만 또 며칠 후에 다시 시도해 보니 동작하는 것을 보고.runJavascript()
에 대한 믿음이 떨어졌고 상태변화와 시점을 정확하게 파악하기 위해 비동기를 동기로 바꾸고 싶어 방법을 변경하게 되었습니다.
문제 해결에 앞서 현재 만들려고 하는 애플리케이션이 Flutter의 웹뷰로 웹/앱을 내장하는 방식으로 개발을 진행하기 때문에 웹뷰, 웹/앱 부터 현재 애플리케이션이 어떻게 구성되어 있는지 이해하면 더 좋을 것 같아서 작성하게 되었습니다. 😄
웹뷰와 웹/앱
웹뷰(WebView)
안드로이드 및 iOS 개발에 주로 사용되며 모바일 애플리케이션 내부에 웹 컨텐츠를 표시하기 위한 컴포넌트입니다. 웹뷰를 통해 모바일 앱 내에서 웹 기술을 사용하여 동적인 컨텐츠를 표시하고나 웹/앱을 내장할 수 있습니다.
웹/앱(Web App)
사용자가 인터넷을 통해 액세스할 수 있는 모든 종류의 앱을 포함하며 웹 브라우저를 통해 실행되는 애플리케이션입니다. 웹/앱은 일반적으로 웹 기술 (HTML, CSS, JavaScript 등)을 사용하여 개발되며, 모바일 브라우저나 데스크톱 브라우저를 통해 실행됩니다.
애플리케이션 설명
웹뷰로 웹/앱을 내장한 애플리케이션을 만들기 위해 개발한 웹을 서버에 띄우고 Flutter의 webview_flutter
라이브러리를 통해 구현하였습니다.
추가로 웹뷰로 웹/앱을 내장하는 것 같이 서버에 띄어져 있는 웹을 계속 보는 것이 아닌 서버에 띄어져 있는 웹에 대한 정보들을 가져와서 Flutter에 내장하는 방식입니다.
비동기 상황 (현재)
맨 처음 이야기한 것처럼 webview_flutter
라이브러리를 사용하면서 Flutter를 이용하여 Device의 위치 기능, 정보 등 다양한 기능을 사용하기 위해 webview_flutter
의 내장 함수 ..addJavaScriptChannel()
로 post Message를 보내고 .runJavascript()
를 통해 웹의 JavaScript를 동작하여 양방향 통신을 비동기로 진행하였습니다.
그림을 보면 webview_flutter
의 라이브러리를 통해서 양방향 통신을 한 것을 볼 수 있습니다.
[이미지] 이미이미3
코드 (main.dart)
사용 라이브러리
geolocator
: ^10.0.0 //Device의 경도, 위도를 파악webview_flutter
: ^4.2.2 //웹뷰 실행
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:geolocator/geolocator.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized(); //앱 실행할 준비가 완료될 때까지 기다린다.
runApp(const MyHomePage());
}
class MyHomePage extends StatelessWidget {
static WebViewController controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'Message',
onMessageReceived: (JavaScriptMessage msg) {
var data = jsonDecode(msg.message);
switch (data["cmd"]) {
case "get_location":
await Permission.locationWhenInUse.request(); // 위치 권한 요청 및 설명
getLocation();
}
}
)
..loadRequest("서버에 띄어져 있는 웹 URL");
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '비동기 접근 부분',
home: Scaffold(
body: WebViewWidget(
controller: controller
)
)
);
}
static Future<String> getLocation() async {
LocationPermission permit = await Geolocator.checkPermission();
String permit_str = permit.toString();
if(permit_str != "LocationPermission.denied" && permit_str != "LocationPermission.deniedForever") {
Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
String lat = position.latitude.toString();
String lon = position.longitude.toString();
controller.runJavaScript('window.get_location($lat, $lon)');
} else {
controller.runJavaScript('window.get_location("0", "0")');
}
}
}
웹 JS
function get_location() {
//flutter 통신
Message.postMessage(
JSON.stringify({
cmd: 'get_location',
})
);
//flutter에서 .runJavaScript()로 실행되는 부분
window.get_location = (lat, lon) => {
console.log(lat, lon);
};
}
그래서 되었다가 안 됐다가 하는 부분, 동기적으로 Device의 기능을 사용하기 위해 다음과 같은 방법을 사용하려고 합니다.
동기 상황 (해결책)!!
webview_flutter
을 사용하지만, 웹과 연결하는 것만 사용하고 Flutter 내부에서 Server를 띄워서 URL을 통해 동기적으로 통신하는 방안입니다.
그림을 보면 webview_flutter
에서 web을 내장하고 Server와 통신하는 것을 볼 수 있습니다.
코드
사용 라이브러리
geolocator
: ^10.0.0 //Device의 경도, 위도를 파악webview_flutter
: ^4.2.2 //웹뷰 실행
main.dart
에서 HttpServer
을 통해 Server를 띄워 URL을 통해(API에 접근하는 방식과 같이) 웹에서 fetch
등을 이용하여 양방향 통신을 하며 동기적으로 동작하게 하는 방식입니다.
main.dart
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:geolocator/geolocator.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized(); //앱 실행할 준비가 완료될 때까지 기다린다.
runApp(const MyHomePage());
try {
var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 8080).then((_server) {
_server.listen((HttpRequest request) async {
HttpResponse response = request.response;
response.headers.add("Access-Control-Allow-Origin", "*"); //Cors 처리
response.headers.add("Access-Control-Allow-Methods", "POST,GET,DELETE,PUT,OPTIONS"); //Cors 처리
switch (request.uri.path) {
// uri.path로 접근하게 하기
// 웹의 JS 코드에서 fetch("HttpServer에 쓴 주소 + /get_location")을 통해 접근 가능
case "/get_location":
await Permission.locationWhenInUse.request(); // 위치 권한 요청 및 설명
String location_info = await getLocation();
response.write(location_info);
break;
};
response.close();
});
}); // 서버 띄우기
} catch(e) {
print("mattabu: Something wrong server"+e.toString()+"");
}
}
Future<String> getLocation() async {
LocationPermission permit = await Geolocator.checkPermission();
String permit_str = permit.toString();
if(permit_str != "LocationPermission.denied" && permit_str != "LocationPermission.deniedForever") {
Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
String lat = position.latitude.toString();
String lon = position.longitude.toString();
return '{"LATITUDE": "'+lat+'", "LONGITUDE": "'+lon+'"}';
} else {
return '{"LATITUDE": "0", "LONGITUDE": "0"}';
}
}
class MyHomePage extends StatelessWidget {
static WebViewController controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..loadRequest("서버에 띄어져 있는 웹 URL"); //webview에 웹 내장시키기
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '동기 접근 부분',
home: Scaffold(
body: WebViewWidget(
controller: controller
)
)
);
}
}
웹 JS
다음과 같이 접근하여 값을 얻을 수 있습니다.
function flutter_get_location() {
try {
const response = await fetch("http://localhost:8080/get_location", {
method: "GET",
});
const result = await response.json();
return result;
} catch (xhr) {
// fetch로 접근 조차 실패한 부분만 error
console.error("request 에러:", xhr);
}
}
마치며
웹뷰 개발을 진행하면서 안드로이드, iOS 모든 상황에서 똑같이 동작하게 해야 하며 CSS도 많이 수정하면서 여러 개를 알면서 좋았고 웹이라면 express
와 같은 Server를 띄워서 URL로 전역에서 관리할 방법을 flutter 내부에서도 사용하니 신선했습니다.
그러나 서버를 띄운다는 것은 유지 보수, 복잡성, 대기시간, 보안등 많은 문제를 야기하니 좀 더 생각하며 기술을 선택해야겠다는 생각이 많이 들었고 새로운 방법이 있으면 배우고 싶습니다. 😄
기본적인 방법 이외의 것을 개발하게 되어 나누고 싶었고 좀 더 오래 기억하고 싶어 글을 작성하게 되었는데 약간 회사의 제품 관련 코드여서 온전하게 다 보여드릴 수 없는 점이 아쉬운 것 같습니다.
도움이 필요하신 분들은 댓글이나 이메일로 연락해 주세요. 😸
긴 글 읽어주셔서 감사합니다.
REF
https://medium.com/@naik.rpsn/http-server-running-on-a-mobile-app-with-flutter-1ef1e717dda1
https://medium.com/@naik.rpsn/http-server-running-on-a-mobile-app-with-flutter-1ef1e717dda1
https://api.flutter.dev/flutter/dart-io/HttpServer-class.html
https://nhj12311.tistory.com/595
https://api.flutter.dev/flutter/dart-io/HttpRequest-class.html
https://stackoverflow.com/questions/10456591/cors-with-dart-how-do-i-get-it-to-work
https://dart-ko.dev/language
'Tip and Error' 카테고리의 다른 글
데스크톱 앱 위치, 사이즈 저장 (NW.js) (2) | 2024.01.26 |
---|---|
[Flutter, Android] FCM (push notification) (2) | 2023.11.25 |
많은 데이터로 인한 문제 (무한 스크롤 성능 개선) (4) | 2023.09.16 |
ESLint, Prettier 적용 (IntelliJ, WebStorm) (0) | 2023.09.08 |
[Error] Failed to load resource: the server responded with a status of 403 () (3) | 2023.07.26 |