flutter 네이티브와 웹뷰와 데이터 통신하는 방법을 알아보도록 하겠습니다.
webview 에서 버튼을 클릭하면 javascript 함수에서 flutter 를 호출합니다.
![](https://mimasgo.com/wp-content/uploads/2023/02/스크린샷-2023-02-20-오후-5.04.17.png)
버튼 클릭시 webviewjsbridge 로 전송됩니다.
![](https://mimasgo.com/wp-content/uploads/2023/02/스크린샷-2023-02-20-오후-5.04.41-1024x80.png)
![](https://mimasgo.com/wp-content/uploads/2023/02/스크린샷-2023-02-20-오후-5.08.29.png)
버튼 클릭시 webviewjsbridge 로 전송됩니다.
![](https://mimasgo.com/wp-content/uploads/2023/02/스크린샷-2023-02-20-오후-5.09.08-1024x139.png)
decoding 해보겠습니다.
![](https://mimasgo.com/wp-content/uploads/2023/02/스크린샷-2023-02-20-오후-5.05.33.png)
웹페이지의 javascript 에서 보낸 json 이 출력됩니다.
/lib/main.dart
/*
https://github.com/flutter/flutter/issues/117333
https://pub.dev/packages/webview_javascript_bridge
https://blog.steinjun.net/post/8
*/
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_javascript_bridge/webview_javascript_bridge.dart';
void main() {
runApp(
const MaterialApp(
home: WebViewApp(),
),
);
}
class WebViewApp extends StatefulWidget {
const WebViewApp({super.key});
@override
State<WebViewApp> createState() => _WebViewAppState();
}
class _WebViewAppState extends State<WebViewApp> {
late final WebViewController controller;
late final WebViewJavaScriptBridge _bridge;
@override
void initState() {
super.initState();
_bridge = WebViewJavaScriptBridge();
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
// Update loading bar.
},
onPageStarted: (String url) {},
onPageFinished: (String url) {},
onWebResourceError: (WebResourceError error) {},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
..addJavaScriptChannel(
webviewJavaScriptBridgeChannel,
onMessageReceived: (JavaScriptMessage message) {
debugPrint(message.message);
}
)
..loadRequest(
Uri.parse('http://220.72.212.247:9286/'),
);
print(">>> message >>>>");
_bridge.updateWebViewController(controller);
/*
_bridge.addMessageHandler(ClosureMessageHandler(
resolver: (message, controller) => message.action == "toaster",
handler: (message, controller) {
// TODO: show the toaster
print("message22222");
print(message);
return null;
},
));
*/
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter WebView'),
),
body: WebViewWidget(
controller: controller,
),
);
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1 id="title">This is web title</h1>
<button type="button" onclick="sendingMessage()">send message to flutter</button>
<script src="app.js"></script>
</script>
<h1>index.php ㅇㅇ 333 </h1>8.0.32-0ubuntu0.20.04.2
</body>
</html>
app.js
//import webViewJavaScriptBridge from './node_modules/webview-javascript-bridge/dist/index.cjs.js';
//import webViewJavaScriptBridge from './node_modules/webview-javascript-bridge/dist/index.esm.js';
class JSONUriEncoder {
constructor(scheme = "webviewjsbridge", host = "stormyang.cn") {
this.scheme = scheme;
this.host = host;
}
encode(args) {
const message = {
channel: args.channel,
action: args.action,
params: args.params,
callbackId: args.callbackId,
};
return `${this.scheme}://${this.host}?args=${encodeURIComponent(JSON.stringify(message))}`;
}
}
class WebViewJavaScriptBridge {
constructor() {
this.callbackId = 1;
this.callbacks = new Map();
this.handlers = new Map();
this.encoder = new JSONUriEncoder();
}
sendMessage(message) {
return new Promise((resolve, reject) => {
if (!message.action || message.action.length <= 0) {
reject('action is invalid');
return;
}
const channel = message.channel || 'default';
const channelImp = window[channel];
if (channelImp === null || channelImp === undefined) {
reject(`
channel named "${channel}" not found in flutter. please add channel:
late final _webviewController = WebViewController()
...
..addJavaScriptChannel(webviewJavaScriptBridgeChannel,
onMessageReceived: _bridge.receiveMessage)
...
`);
return;
}
const callbackId = this._pushCallback(resolve);
const encoded = this.encoder.encode({
channel,
action: message.action,
params: message.params,
callbackId,
});
if (!encoded) {
reject(`Unable build message. (channel: ${channel}, action: ${message.action}, params: ${message.params})`);
return;
}
this._log('sending message:', encoded);
channelImp.postMessage(encoded);
});
}
registerMessageHandler(id, handler) {
if (id === null || id === undefined) {
return;
}
if (typeof handler === 'function') {
this.handlers.set(id, handler);
}
else {
this.unregisterMessageHandler(id);
}
}
unregisterMessageHandler(id) {
this.handlers.delete(id);
}
setEncoder(encoder) {
this.encoder = encoder;
}
setLogger(logger) {
this.logger = logger;
}
_log(...args) {
var _a;
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.call(this.logger, ...args);
}
_pushCallback(cb) {
const id = this.callbackId++;
const key = `cb_${id}`;
this.callbacks.set(key, cb);
return key;
}
_popCallback(id) {
if (this.callbacks.has(id)) {
const cb = this.callbacks.get(id);
this.callbacks.delete(id);
return cb;
}
}
_receiveMessage(message) {
this._log(`receive message, id: ${message.id}, callbackId: ${message.callbackId}, params:`, message.params);
if (message.callbackId) {
this._log('this message is a callback');
const cb = this._popCallback(message.callbackId);
if (cb) {
cb(message.params);
return true;
}
return false;
}
const key = message.id;
if (key) {
this._log('this message is a calling to javascript');
const func = this.handlers.get(key);
if (typeof func !== 'function') {
return `no handler for message: ${message.id}`;
}
let ret = func(message.params);
if (typeof ret === 'object' && ret !== null) {
ret = JSON.stringify(ret);
}
return ret;
}
throw Error('message must have a id or callbackId.');
}
}
class UriEncoder {
constructor(scheme = 'webviewjsbridge') {
this.scheme = scheme;
}
encode(args) {
let message = `${this.scheme}://${args.channel}/${args.action}`;
const query = [];
if (args.params) {
const argsJSON = encodeURIComponent(JSON.stringify(args.params));
query.push({ key: 'args', value: argsJSON });
}
if (args.callbackId) {
query.push({ key: 'callbackId', value: args.callbackId });
}
if (query.length > 0) {
message += '?';
message += query.map((pair) => `${pair.key}=${pair.value}`).join('&');
}
return message;
}
}
class JSONEncoder {
encode(args) {
const message = {
channel: args.channel,
action: args.action,
params: args.params,
callbackId: args.callbackId,
};
return JSON.stringify(message);
}
}
const webViewJavaScriptBridge = new WebViewJavaScriptBridge();
window.webViewJavaScriptBridge = webViewJavaScriptBridge;
async function sendingMessage() {
let response = await webViewJavaScriptBridge.sendMessage({
action: 'tester',
params: 123456,
});
console.log("tester's response", response);
}