Flutter vue webview_flutter JS交互

  • Flutter 应用通过 webview_flutter WebView加载网页
  • Flutter 主动调用js 通过runJavascript 方法(没有callback, 有知道的分享下)
  • JS 调用Flutter然后Flutter返回结果给JS 通过navigationDelegate 拦截的方式, 通过window.记录对象添加id来记录callback, 区分不同的回调

webview加载vue nom run serve 本地网页

import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:kgab/utils/bridge.dart';
import 'package:webview_flutter/webview_flutter.dart';

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State {
  final Completer _controller =
      Completer();
  //是否展示原生导航栏
  bool showAppBar = false;

  @override
  void initState() {
    super.initState();
    if (Platform.isAndroid) {
      WebView.platform = SurfaceAndroidWebView();
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: showAppBar
                ? AppBar(
                    title: const Text('Plugin example app'),
                  )
                : null,
            floatingActionButton: FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () {
                Bridge.callJS(_controller);
              },
            ),
            body: Builder(builder: (BuildContext context) {
              return WebView(
                initialUrl: 'http://localhost:8080',
                javascriptMode: JavascriptMode.unrestricted,
                onWebViewCreated: (WebViewController webViewController) {
                  _controller.complete(webViewController);
                },
                onProgress: (int progress) {
                  print('WebView is loading (progress : $progress%)');
                },
                javascriptChannels: {
                  _toasterJavascriptChannel(context),
                },
                navigationDelegate: (NavigationRequest request) {
                  if (request.url.startsWith('jscallnative')) {
                    Bridge.preventUrl(request.url, _controller);
                    return NavigationDecision.prevent;
                  }
                  return NavigationDecision.navigate;
                },
                onPageStarted: (String url) {
                  print('Page started loading: $url');
                },
                onPageFinished: (String url) {
                  print('Page finished loading: $url');
                },
                gestureNavigationEnabled: true,
                backgroundColor: const Color(0x00000000),
              );
            })));
  }

  JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
    return JavascriptChannel(
        name: 'Toaster',
        onMessageReceived: (JavascriptMessage message) {
          // ignore: deprecated_member_use
          Scaffold.of(context).showSnackBar(
            SnackBar(content: Text(message.message)),
          );
        });
  }
}

JSBridge.dart

import 'dart:async';
import 'dart:convert' as convert;
import 'package:kgab/models/js_prevent_model.dart';
import 'package:webview_flutter/webview_flutter.dart';

class Bridge {
  static preventUrl(
      String url, Completer _controller) async {
    print('拦截---${url}');
    WebViewController controller = await _controller.future;
    Uri uri = Uri.dataFromString(url);
    Map params = uri.queryParameters;
    JsPreventModel model = JsPreventModel.fromJs(params);
    print("拦截请求参数:${params.toString()}");
    Map backParams = {"backParams": "nativeBack"};
    print(
        "runJavascript---window.${model.successBack!}('${model.guid!}', ${backParams.toString()})");
    controller.runJavascript(
        "window.${model.successBack!}('${model.guid!}', '${convert.jsonEncode(backParams)}')");
  }

  static callJS(Completer _controller) async {
    WebViewController controller = await _controller.future;
    controller.runJavascript("window.waitNativeCallBack('test')");
  }
}

JsPreventModel 拦截的Model

class JsPreventModel {
  String? guid;
  String? methodName;
  String? param;
  String? successBack;
  String? errorBack;

  JsPreventModel({
    this.guid,
    this.methodName,
    this.param,
    this.successBack,
    this.errorBack,
  });

  @override
  String toString() {
    return 'JsPreventModel(guid: $guid, apiname: $methodName, param: $param, successBack: $successBack, errorBack: $errorBack)';
  }

  factory JsPreventModel.fromJs(Map json) {
    return JsPreventModel(
      guid: json['guid'] as String?,
      methodName: json['methodName'] as String?,
      param: json['param'] as String?,
      successBack: json['successBack'] as String?,
      errorBack: json['errorBack'] as String?,
    );
  }

  Map
      toJsfunctionInvokeIdApinameParamOncallbackErrorcallback() {
    return {
      'guid': guid,
      'methodName': methodName,
      'param': param,
      'oncallback': successBack,
      'errorcallback': errorBack,
    };
  }
}

vue brdige.js


function createGuid() {
    var d = new Date().getTime();
    var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });
    return uuid;
}

/*
    guid 唯一标识, 用来 window.JSCallNativeCallBack.callBackCache 记录回调函数
    methodName 调用原生的方法名, 用来给原生区别调用的功能模块
    param 传给原生的参数 string
    successCallBack failedCallBack Flutter通过执行 controller.runJavascript(window.JSCallNativeCallBack.successCallBack(guid, 参数))来回调到js
*/
function _execute(guid, methodName, param, successCallBack, failedCallBack) {
    try {
        if (param && typeof param !== 'string') {
            param = JSON.stringify(param);
        }
    } catch (e) {
        throw new Error(e.toString());
    }
    let src = `jscallnative://?guid=${guid}&methodName=${methodName}¶m=${encodeURIComponent(param)}&successBack=${successCallBack}&errorBack=${failedCallBack}`;
    
    let element = document.createElement('iframe')
    element.setAttribute('src', src)
    element.setAttribute('style', 'display:none')
    document.body.appendChild(element)
    element.parentNode.removeChild(element)
    console.info('guid', guid)
}

window.JSCallNativeCallBack = {
    callBackCache: {},
    successCallBack: function(guid, data) {
        this.callBackCache[guid].successCallBack(data)
        delete this.callBackCache[guid]
    },
    failedCallBack: function(guid, data) {
        this.callBackCache[guid].failedCallBack(data)
        delete this.callBackCache[guid]
    }
}

//Flutter 通过 controller.runJavascript(window.JSCallNativeCallBack(参数))调用js
window.waitNativeCallBack = function(message){
    //此处等待原生发送的消息
    
}

function JSCallNativeFactory(guid, methodName, param, successCallBack, failedCallBack) {
    if (typeof successCallBack !== 'function' || typeof failedCallBack !== 'function') {
        throw new Error('callback must be a function')
    }
    this.successCallBack = successCallBack
    this.failedCallBack = failedCallBack
    _execute(guid, methodName, param, 'JSCallNativeCallBack.successCallBack', 'JSCallNativeCallBack.failedCallBack')
}

window.JSCallNative = function(name, extraParams) {
    if (!name) {
        console.log('输入方法名')
        return
    }
    extraParams = extraParams || {}
    let param = extraParams.param || ''
    let successCallBack = extraParams.successCallBack || function() {}
    let failedCallBack = extraParams.failedCallBack || function(msg) {
        throw new Error(msg)
    }
    let guid = createGuid()
    window.JSCallNativeCallBack.callBackCache[guid] = new JSCallNativeFactory(guid, name, param,
        successCallBack,
        failedCallBack)
}
export default window.JSCallNative

App.vue里面的调用


import JSCallNative from './common/bridge.js';

JSCallNative('test',{
    param: '123456',
    successCallBack: function(data) {
        Toast(data);
    },
    failedCallBack: function(msg) {
        Toast(JSON.stringify(msg));
    }
})

你可能感兴趣的:(Flutter vue webview_flutter JS交互)