Flutter JavaScript交互插件flutter_jsbridge_sdk的使用

发布于 1周前 作者 bupafengyu 来自 Flutter

Flutter JavaScript交互插件 flutter_jsbridge_sdk 的使用

介绍

flutter_jsbridge_sdk 是一个轻量级的 jsbridge,用于在 WebView 中的 Flutter 和 JavaScript 之间发送消息。

功能

  • 不依赖于 webview_flutter
  • 支持查看消息日志(调试模式)
  • 支持注册方法
  • 支持取消注册方法
  • 支持 Flutter 和 JavaScript 之间方法互相调用,传递参数、接收返回结果

示例截图

开始

在工程的 pubspec.yaml 文件中添加 flutter_jsbridge_sdk 插件:

dependencies:
  flutter_jsbridge_sdk: ^1.0.2

Flutter 用法

初始化配置

在 WebView 创建时进行初始化配置:

WebView(
  onWebViewCreated: (WebViewController controller) {
    // 配置jsBridge
    jsBridge.init(
      messageRunner: controller.runJavascript,
      debug: true,
    );
  },
  initialUrl: _initialUrl!,
  javascriptMode: JavascriptMode.unrestricted,
  javascriptChannels: <JavascriptChannel>{
    // 配置JavascriptChannel
    JavascriptChannel(
      name: jsBridge.channelName,
      onMessageReceived: (JavascriptMessage message) {
        jsBridge.onMessageReceived(message.message);
      },
    ),
  },
  navigationDelegate: (NavigationRequest navigation) {
    return NavigationDecision.navigate;
  },
)

配置 jsBridge

jsBridge.init(
  messageRunner: controller.runJavascript,
  debug: true,
);
参数 说明 默认值和类型 必传
debug 调试模式 false(Boolean)
messageRunner 提供 Flutter 执行 JS 代码的能力,使用 WebViewController 的 runJavascript 即可 (JSBridgeMessageRunner)

配置 JavascriptChannel

JavascriptChannel(
  name: jsBridge.channelName, // 必须使用 jsBridge.channelName
  onMessageReceived: (JavascriptMessage message) {
    jsBridge.onMessageReceived(message.message);
  },
),

注册方法

jsBridge.registerHandler<String>('FlutterEcho', (Object? data) async {
  // 返回成功或失败信息
  return Future.error('fail response from flutter');
});
参数 说明 默认值和类型 必传
handlerName 注册的方法名称 (String)
handler 注册的方法实现,返回 Future
data: 发送过来的数据
Future.value: Flutter 端业务处理成功时通知 JS 端
Future.error: Flutter 端业务处理失败时通知 JS 端
(JSBridgeHandler)

取消注册方法

jsBridge.unregisterHandler('FlutterEcho');
参数 说明 默认值和类型 必传
handlerName 注册的方法名称 (void)

调用方法

try {
  final String data = await jsBridge.callHandler<String>(
    'JSEcho',
    data: 'request from flutter',
  );
  _log('[call handler] success response: $data');
} catch (err) {
  _log('[call handler] fail response: $err');
}
参数 说明 默认值和类型 必传
handlerName 调用的方法名称 (String)
data 参数
data: 发送过来的数据
(Object?)
return 返回 Future 对象
Future.value: JS 端业务处理成功时的回调
Future.error: JS 端业务处理失败时的回调
(Future)

JS 用法

JS 端的使用基本跟 Flutter 保持一致,具体参考 javascript-jsbridge-sdk

完整示例 Demo

以下是一个完整的示例代码,展示了如何使用 flutter_jsbridge_sdk 插件进行 Flutter 和 JavaScript 之间的交互:

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_jsbridge_sdk/flutter_jsbridge_sdk.dart';
import 'package:local_assets_server/local_assets_server.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late final WebViewController _webViewController;
  String _logString = '';

  void _initLocalAssetsServer() {
    final LocalAssetsServer server = LocalAssetsServer(
      address: InternetAddress.loopbackIPv4,
      assetsBasePath: 'assets/',
      logger: const DebugLogger(),
    );
    server.serve().then((final InternetAddress value) {
      final String url =
          'http://${value.address}:${server.boundPort!}/example.html';
      _webViewController.loadRequest(Uri.parse(url));
    });
  }

  void _initWebViewController() {
    late final PlatformWebViewControllerCreationParams params;
    if (WebViewPlatform.instance is WebKitWebViewPlatform) {
      params = WebKitWebViewControllerCreationParams(
        allowsInlineMediaPlayback: true,
        mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
      );
    } else if (WebViewPlatform.instance is AndroidWebViewPlatform) {
      params = AndroidWebViewControllerCreationParams();
    } else {
      params = const PlatformWebViewControllerCreationParams();
    }

    final WebViewController controller =
        WebViewController.fromPlatformCreationParams(params);

    final NavigationDelegate navigationDelegate = NavigationDelegate(
      onProgress: (int progress) {
        debugPrint('WebView is loading (progress : $progress%)');
      },
      onPageStarted: (String url) {
        debugPrint('Page started loading: $url');
      },
      onPageFinished: (String url) {
        debugPrint('Page finished loading: $url');
      },
      onWebResourceError: (WebResourceError error) {
        debugPrint('''
Page resource error:
  code: ${error.errorCode}
  description: ${error.description}
  errorType: ${error.errorType}
  isForMainFrame: ${error.isForMainFrame}
          ''');
      },
      onNavigationRequest: (NavigationRequest request) {
        if (request.url.startsWith('https://www.youtube.com/')) {
          debugPrint('blocking navigation to ${request.url}');
          return NavigationDecision.prevent;
        }
        debugPrint('allowing navigation to ${request.url}');
        return NavigationDecision.navigate;
      },
    );

    controller
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0xFFFFFFFF))
      ..setNavigationDelegate(navigationDelegate)
      ..addJavaScriptChannel(
        jsBridge.channelName,
        onMessageReceived: (JavaScriptMessage message) {
          jsBridge.onMessageReceived(message.message);
        },
      );

    if (controller.platform is AndroidWebViewController) {
      AndroidWebViewController.enableDebugging(true);
      (controller.platform as AndroidWebViewController)
          .setMediaPlaybackRequiresUserGesture(false);
    }

    _webViewController = controller;
  }

  void _initJSBridge() {
    jsBridge.init(
      messageExecutor: _webViewController.runJavaScript,
      debug: kDebugMode,
    );
  }

  [@override](/user/override)
  void initState() {
    super.initState();
    _initWebViewController();
    _initLocalAssetsServer();
    _initJSBridge();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('jsbridge sdk')),
      body: Center(
        child: Column(
          children: <Widget>[
            _buildWebView(),
            const SizedBox(height: 12.0),
            _buildButton(),
          ],
        ),
      ),
    );
  }

  Widget _buildWebView() {
    return Expanded(child: WebViewWidget(controller: _webViewController));
  }

  Widget _buildButton() {
    return Expanded(
      child: Container(
        color: Colors.white,
        padding: const EdgeInsets.all(12.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            const Padding(
              padding: EdgeInsets.symmetric(vertical: 12.0),
              child: Text(
                'Flutter',
                style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
              ),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                ElevatedButton(
                  child: const Text('registerHandler'),
                  onPressed: () => _registerHandler(),
                ),
                ElevatedButton(
                  child: const Text('unregisterHandler'),
                  onPressed: () => _unregisterHandler(),
                ),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                ElevatedButton(
                  child: const Text('callHandler'),
                  onPressed: () => _callHandler(),
                ),
                ElevatedButton(
                  child: const Text('callNotExistHandler'),
                  onPressed: () => _callNotExistHandler(),
                ),
              ],
            ),
            Container(
              height: 160,
              width: double.infinity,
              margin: const EdgeInsets.only(top: 12),
              color: const Color.fromRGBO(128, 128, 128, 0.1),
              child: SingleChildScrollView(
                child: Text(_logString, style: const TextStyle(fontSize: 18)),
              ),
            )
          ],
        ),
      ),
    );
  }

  void _log(String msg) {
    if (_logString.isNotEmpty) {
      msg = '$_logString\n$msg';
    }
    setState(() {
      _logString = msg;
    });
    Future.delayed(const Duration(milliseconds: 200), () {
      _controller.animateTo(
        _controller.position.maxScrollExtent,
        duration: kThemeAnimationDuration,
        curve: Curves.linear,
      );
    });
  }

  void _registerHandler() {
    _log('[register handler]');
    jsBridge.registerHandler<String>('FlutterEcho', (Object? data) async {
      return Future.error('fail response from flutter');
    });
  }

  void _unregisterHandler() {
    _log('[unregister handler]');
    jsBridge.unregisterHandler('FlutterEcho');
  }

  Future<void> _callHandler() async {
    _log('[call handler] handlerName: JSEcho, data: request from javascript');
    try {
      final String data = await jsBridge.callHandler<String>(
        'JSEcho',
        data: 'request from flutter',
      );
      _log('[call handler] success response: $data');
    } catch (err) {
      _log('[call handler] fail response: $err');
    }
  }

  Future<void> _callNotExistHandler() async {
    _log(
        '[call handler] handlerName: JSEchoNotExist, data: request from javascript');
    try {
      final String data = await jsBridge.callHandler<String>(
        'JSEchoNotExist',
        data: 'request from flutter',
      );
      _log('[call handler] success response: $data');
    } catch (err) {
      _log('[call handler] fail response: $err');
    }
  }
}

更多关于Flutter JavaScript交互插件flutter_jsbridge_sdk的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter JavaScript交互插件flutter_jsbridge_sdk的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter应用中使用flutter_jsbridge_sdk插件来实现Flutter与JavaScript交互的示例代码。这个插件允许你在Flutter WebView中执行JavaScript代码,并从JavaScript接收消息。

首先,确保你已经在pubspec.yaml文件中添加了flutter_jsbridge_sdk依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_jsbridge_sdk: ^最新版本号  # 请替换为实际的最新版本号

然后,运行flutter pub get来安装依赖。

接下来是一个完整的示例代码,展示如何使用flutter_jsbridge_sdk

import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
import 'package:flutter_jsbridge_sdk/flutter_jsbridge_sdk.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: WebViewPage(),
    );
  }
}

class WebViewPage extends StatefulWidget {
  @override
  _WebViewPageState createState() => _WebViewPageState();
}

class _WebViewPageState extends State<WebViewPage> {
  final FlutterWebviewPlugin _flutterWebviewPlugin = FlutterWebviewPlugin();
  final JsBridge _jsBridge = JsBridge();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter WebView with JS Bridge'),
      ),
      body: WebviewScaffold(
        url: "https://your-webview-url.com",  // 替换为你的WebView URL
        withJavascript: true,
        withLocalStorage: true,
        hidden: true,
        initialChild: Center(
          child: CircularProgressIndicator(),
        ),
        webviewPlugin: _flutterWebviewPlugin,
        appBar: AppBar(
          title: Text('WebView Example'),
        ),
        onWebViewCreated: (WebViewController webViewController) async {
          // 设置JS桥接监听器
          _jsBridge.setHandler((call) async {
            print("Received message from JS: ${call.message}");
            // 这里可以处理从JavaScript传来的消息
            // 比如,可以返回一些数据给JavaScript
            return "Hello from Flutter!";
          });

          // 注册一个JS接口供JavaScript调用
          await _flutterWebviewPlugin.evalJavascript(
            '''
            window.flutterBridge = {
              postMessage: function(message) {
                // 使用自定义协议触发Flutter的监听器
                window.location.href = 'flutter-js-bridge://' + encodeURIComponent(JSON.stringify(message));
              }
            };
            ''',
          );

          // 监听来自WebView的URL变化,以捕获自定义协议的消息
          _flutterWebviewPlugin.onUrlChanged.listen((String url) async {
            if (url.startsWith('flutter-js-bridge://')) {
              String message = Uri.decodeComponent(url.split('://')[1]);
              print("Received message via URL: $message");
              // 可以在这里处理从JavaScript通过自定义协议传来的消息
            }
          });
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // 从Flutter向WebView发送消息
          await _flutterWebviewPlugin.evalJavascript(
            '''
            if (window.flutterBridge && typeof window.flutterBridge.postMessage === 'function') {
              window.flutterBridge.postMessage('Hello from Flutter to WebView!');
            }
            ''',
          );
        },
        tooltip: 'Send message to WebView',
        child: Icon(Icons.send),
      ),
    );
  }

  @override
  void dispose() {
    _flutterWebviewPlugin.dispose();
    _jsBridge.dispose();
    super.dispose();
  }
}

注意事项:

  1. 依赖冲突:确保flutter_webview_pluginflutter_jsbridge_sdk版本兼容。flutter_jsbridge_sdk可能依赖于特定的flutter_webview_plugin版本。
  2. 自定义协议:在示例中,我们通过自定义协议flutter-js-bridge://来在WebView和Flutter之间传递消息。这是一种常见的方法,但你也可以根据自己的需求选择其他方法。
  3. 权限:确保你的WebView URL允许JavaScript执行和跨域请求(如果需要)。

调试与测试:

  • 确保WebView加载的页面中有JavaScript代码调用window.flutterBridge.postMessage来发送消息。
  • 在Flutter端,检查控制台输出,看看是否成功接收到了来自WebView的消息。

希望这个示例代码能帮助你理解如何在Flutter应用中使用flutter_jsbridge_sdk来实现Flutter与JavaScript的交互。

回到顶部