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<Home> {
  final Completer<WebViewController> _controller =
      Completer<WebViewController>();
  //是否展示原生导航栏
  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: <JavascriptChannel>{
                  _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<WebViewController> _controller) async {
    print('拦截---${url}');
    WebViewController controller = await _controller.future;
    Uri uri = Uri.dataFromString(url);
    Map<String, String> params = uri.queryParameters;
    JsPreventModel model = JsPreventModel.fromJs(params);
    print("拦截请求参数:${params.toString()}");
    Map<String, String> 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<WebViewController> _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<String, dynamic> 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<String, dynamic>
      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}&param=${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));
    }
})

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容