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
GitHub - niwaguan/webview-javascript-bridge: A bridge for JavaScript communicate with webview_flutter
A bridge for JavaScript communicate with webview_flutter - GitHub - niwaguan/webview-javascript-bridge: A bridge for JavaScript communicate with webview_flutter
github.com
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 |