flutter 네이티브와 웹뷰와 데이터 통신하는 방법을 알아보도록 하겠습니다.
webview 에서 버튼을 클릭하면 javascript 함수에서 flutter 를 호출합니다.
버튼 클릭시 webviewjsbridge 로 전송됩니다.
decoding 해보겠습니다.
웹페이지의 javascript 에서 보낸 json 이 출력됩니다.
. webview -> 네이티브
/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);
}
참고 https://github.com/niwaguan/webview-javascript-bridge/tree/master/example/flutter
main.dart
import 'package:flutter/material.dart';
import 'package:webview_in_flutter/navigator_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: const NavigatorPage(),
);
}
}
navigator_page.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:webview_in_flutter/browser_page.dart';
class NavigatorPage extends StatelessWidget {
const NavigatorPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CupertinoButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (BuildContext context) {
return const BrowserPage();
}),
);
},
child: const Text('Go to Browser')),
),
);
}
}
browser_page.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_javascript_bridge/webview_javascript_bridge.dart';
class BrowserPage extends StatefulWidget {
const BrowserPage({Key? key}) : super(key: key);
@override
State<BrowserPage> createState() => _BrowserPageState();
}
class _BrowserPageState extends State<BrowserPage> {
/// the bridge for webview and javascript
late final WebViewJavaScriptBridge _bridge = WebViewJavaScriptBridge();
/// the WebViewController
late final _webviewController = 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: _bridge.receiveMessage)
..loadRequest(Uri.parse('http://220.72.212.247:9286/'));
@override
void initState() {
super.initState();
_bridge.updateWebViewController(_webviewController);
_bridge.addMessageHandler(ClosureMessageHandler(
resolver: (message, controller) => message.action == "tester",
handler: (message, controller) {
print(message);
return null;
},
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('WebView'),
actions: [
IconButton(
onPressed: _reload,
icon: const Icon(Icons.refresh_outlined),
),
],
),
body: Column(
children: [
SizedBox(
height: 44,
child: Wrap(
children: [
TextButton(
onPressed: () async {
final ret = await _bridge.sendMessage<Map<String, dynamic>>(
function: 'jsFunction');
print("got value: $ret");
},
child: const Text('call JavaScript'),
),
],
),
),
Expanded(
child: WebViewWidget(
controller: _webviewController,
),
)
],
),
);
}
@override
void reassemble() {
super.reassemble();
_reload();
}
_reload() {
_webviewController.reload();
}
}
pubspec.yaml
name: webview_in_flutter
description: A new Flutter project.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: '>=2.19.1 <3.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
webview_flutter: ^4.0.2
webview_javascript_bridge: ^2.0.0
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
'프로그래밍 > flutter' 카테고리의 다른 글
flutter socket 연결후 데이타 계속 보내기 mymq 연결후 응답받음 (0) | 2023.06.28 |
---|---|
flutter dart 에서 hexString 을 byte 로 변환 (소켓한번만연결) (0) | 2023.06.28 |
flutter Tcp Socket 가장 간단한 샘플 (0) | 2023.06.28 |
flutter 앱에 webView 추가 방법 (0) | 2023.02.16 |
Flutter 이용하여 TCP socket 연결 샘플 코드 (0) | 2023.02.16 |