2023. 7. 15. 14:58ㆍ모바일어플개발/Flutter
안녕하세요~ totally 개발자입니다.
WebView JavaScript Communication
Flutter로 개발을 하다보면 웹뷰를 통해서 구현하는 경우가 필요합니다. 웹뷰를 통해서 구현할 때 웹 쪽과 플러터 앱 사이에 통신이 필요한 경우가 있습니다. 통신한다는 이야기는 데이터를 주고 받는 것을 말하며 주로 url 뒤에 데이터를 붙여 parameter 값을 넘기거나 웹 쪽의 자바스크립트 함수를 호출하는 방법이 있습니다. 이 포스팅에서는 자바스크립트 함수를 호출하는 방법을 통해 통신해보도록 하겠습니다.
사용한 패키지는 webview_flutter이며 최신 버전을 사용하면 아래 방법으로 구현이 안 되기 때문에 조금 이전 버전을 사용해야 하며 저는 3.0.4 버전을 사용하였습니다. 참고로 웹뷰로 사용할 웹사이트는 호스팅이 되어 있는 상태여야 가능하므로 준비해주셔야 합니다. 저는 000webhost를 사용하였습니다.
Step 1: pubspec.yaml에 webview_flutter 패키지 3.0.4 버전을 추가하고 Command(Ctrl) + S 또는 터미널에 flutter pub get 해줍니다.

Step 2: 웹뷰로 사용할 파일 web_view_test.html을 생성하시고 다음처럼 자바스크립트 함수를 작성하여 줍니다. 71-73번째 라인은 앱에서 웹으로 데이터를 전달할 함수이며 75-77번째 라인은 웹에서 앱으로 데이터를 전달할 함수입니다. 아래에서 보시겠지만, 웹에서 앱으로 데이터를 전달할 때에는 플러터의 javascriptChannels를 통해 데이터를 전달할 수 있습니다. 그 때 필요한 name을 지정한 것이 toApp이며 (이름 변경 가능) postMessage를 통해 데이터를 전달할 수 있습니다.

그리고 아래에서 메시지를 출력해줄 div 태그와 버튼을 만들어 onclick 속성을 통해 함수를 호출할 수 있도록 해줍니다.

Step 3: webViewPage.dart 파일을 만드신 후에 2번째 라인에 webview_flutter를 임포트해주시고 12번째 라인처럼 WebViewController 변수를 선언해줍니다.

Step 4: WebView 위젯을 만들어주시고 아래처럼 구성해주시면 됩니다. 설명을 스크린샷에 달아놓았습니다. 웹뷰에서 버튼을 눌러 함수를 호출하고 난 다음 메인 페이지로 돌아올 수 있도록 구성합니다.

Step 5: 웹뷰를 호출할 수 있는 메인 페이지를 만들어줍니다.

Step 6: 실행하여 테스트해본 모습입니다.



위처럼 console에도 메시지가 잘 나오면 성공입니다.
[전체 소스 코드]
[web_view_test.html]
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>My Website</title> | |
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"> | |
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script> | |
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script> | |
<style> | |
body { | |
font-family: Arial, sans-serif; | |
background-color: #f2f2f2; | |
color: #333; | |
margin: 0; | |
padding: 0; | |
} | |
</style> | |
<script> | |
function fromAppToWeb(msg) { | |
document.querySelector("#flutterMessageTitle").innerText = msg; | |
} | |
function fromWebToApp(msg) { | |
toApp.postMessage(msg); | |
} | |
</script> | |
</head> | |
<body> | |
<!-- Navigation bar --> | |
<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> | |
<a class="navbar-brand" href="index.html">My Website</a> | |
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> | |
<span class="navbar-toggler-icon"></span> | |
</button> | |
<div class="collapse navbar-collapse" id="navbarNav"> | |
<ul class="navbar-nav"> | |
<li class="nav-item active"> | |
<a class="nav-link" href="index.html">Home</a> | |
</li> | |
<li class="nav-item"> | |
<a class="nav-link" href="about.html">About</a> | |
</li> | |
<li class="nav-item"> | |
<a class="nav-link" href="contact.html">Contact</a> | |
</li> | |
</ul> | |
</div> | |
</nav> | |
<br /> | |
<div id="flutterMessageTitle" style="text-align:center;"> | |
</div> | |
<br /> | |
<div style="text-align:center;"> | |
<button class="btn btn-primary" onclick="fromWebToApp('JAVASCRIPT WEB MESSAGE')">돌아가기</button> | |
</div> | |
</body> | |
</html> |
[webviewPage.dart]
import 'package:flutter/material.dart'; | |
import 'package:webview_flutter/webview_flutter.dart'; | |
class WebViewPage extends StatefulWidget { | |
const WebViewPage({super.key}); | |
@override | |
State<WebViewPage> createState() => _WebViewPageState(); | |
} | |
class _WebViewPageState extends State<WebViewPage> { | |
late final WebViewController _controller; | |
@override | |
void initState() { | |
super.initState(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: SafeArea( | |
child: WebView( | |
initialUrl: | |
"https://testprojectmanager.000webhostapp.com/web_view_test.html", | |
onWebViewCreated: (controller) { | |
_controller = controller; | |
}, | |
onPageFinished: (url) { | |
String message = "FLUTTER MESSAGE"; | |
_controller.runJavascript('fromAppToWeb("$message")'); | |
print("플러터앱에서 웹으로 메시지 전송: $message"); | |
}, | |
javascriptMode: JavascriptMode.unrestricted, | |
javascriptChannels: <JavascriptChannel>{ | |
JavascriptChannel( | |
name: 'toApp', | |
onMessageReceived: (JavascriptMessage message) { | |
print("웹에서 플러터앱으로 메시지 전송: ${message.message}"); | |
Navigator.of(context).pop(); | |
}), | |
}, | |
), | |
), | |
); | |
} | |
} |
[main.dart]
import 'package:flutter/material.dart'; | |
import 'package:project/webviewPage.dart'; | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return const MaterialApp( | |
title: 'Flutter App', | |
home: MainPage(), | |
); | |
} | |
} | |
class MainPage extends StatefulWidget { | |
const MainPage({Key? key}) : super(key: key); | |
@override | |
State<MainPage> createState() => _MainPageState(); | |
} | |
class _MainPageState extends State<MainPage> { | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: SafeArea( | |
child: Center( | |
child: ElevatedButton( | |
onPressed: () { | |
Navigator.of(context).push( | |
MaterialPageRoute(builder: (context) => const WebViewPage())); | |
}, | |
child: const Text("웹뷰 호출"), | |
), | |
), | |
), | |
); | |
} | |
} |