Lzyprime blog (Github) was created at 2020.03.06 qq and email: 2383518170

Lambda. :

  • Warehouse address: github.com/lzyprime/fl…

  • git clone -b flutter_webview_demo https://github.com/lzyprime/flutter_demos.git
    Copy the code
  • Plugin: Webview_flutter, current version: ^0.3.19+9

  • Since we need to take into account the webView of both Android and Ios platforms, JS cannot directly return data when calling native, so we save the country through the form curve of callback: after receiving THE JS request, process the data, and then actively call JS related functions.

  • The js call with Flutter can listen for return values

Main parameters and methods

The official example or skip to the source code

// The constructor
const WebView({
    Key key,
    this.onWebViewCreated, // The callback function, WebViewCreatedCallback(WebViewController Controller), returns the WebViewController
    this.initialUrl, // The url to load
    this.javascriptMode = JavascriptMode.disabled, / / js is executed, the default value is not executed, JavascriptMode. Unrestricted. Can't be empty
    this.javascriptChannels, Set 
      
        to the handlers who invoke the flutter. The names of all javascriptchannels must not be repeated
      
    this.navigationDelegate, // Intercept the request and process it. See the source code for details
    this.gestureRecognizers, // Gesture listening and processing, details please see the source code
    this.onPageStarted,  // Start the load callback, PageStartedCallback(String URL)
    this.onPageFinished, // Load the end callback, PageFinishedCallback(String URL)
  
  /// The remaining parameters can be translated into English, please see the source code for details
    this.debuggingEnabled = false.this.gestureNavigationEnabled = false.this.userAgent,
    this.initialMediaPlaybackPolicy =
        AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
  })  : assert(javascriptMode ! =null),
        assert(initialMediaPlaybackPolicy ! =null),
        super(key: key);
Copy the code

webViewController:

OnWebViewCreated returns the current webView controller. The official example is to define a Completer

to implement delayed initialization:

class _WebViewPageState extends State<WebViewPage> {
  final_controller = Completer<WebViewController>(); . WebView( .... onWebViewCreated: (controller) { _controller.complete(controller); },... }Copy the code

Later calls to WebViewController are made via _controller.Future of type Future

, so all calls are asynchronous:

_controller.future.then((controller){
  ...
})
  / / or
 FutureBuilder(
  future: _controller.future,
  builder: (BuildContext context,AsyncSnapshot<WebViewController> controller){
    return (Widget)
  }
)
Copy the code

Main functions:

// loadUrl, currentUrl, canGoBack... Such as function
// Look at the source code, see the function name and comment to know the function

/// js interoperable
Future<String> evaluateJavascript("Js code") // Execute js and receive the return value from js execution
Copy the code

JavascriptChannel

  JavascriptChannel({
    @required this.name, // the name of the variable that is called,
    // If name="Print", js can call flutter via print.postmessage (MSG)
    // Requests are handled in the onMessageReceived function
    
    @required this.onMessageReceived, // Handle the JS request
    // typedef void JavascriptMessageHandler(JavascriptMessage message);
    // Message. message is the MSG passed by js
    // The function returns no value
    
  })  : assert(name ! =null),
        assert(onMessageReceived ! =null),
        assert(_validChannelNames.hasMatch(name));
Copy the code

Encapsulation interoperation

Js calls with Flutter can receive a return value. Js writes a function, and the tune of Flutter is fine. The js call flutter returns no value, so do a little simple encapsulation

Js request package and corresponding package format:

// js request: {guid: String, // to verify the request, the API is returned with the flutter intact: String, // The name of the interface to be requested, the flutter is returned with the data: Object,... . } // Return the API with the flutter: {guid: String, // to verify the request consistency, passed in by JS, return the API with the flutter intact: String, // The name of the interface to request, passed by JS, the flutter returns data: Object,... . }Copy the code

flutter

JavascriptChannel is implemented as an interface to encapsulate a set of apis
// You can also construct it directly

class NativeBridge implements JavascriptChannel {
  BuildContext context; // Derived from the current widget to make it easy to manipulate the UI
  Future<WebViewController> _controller; // The current webView controller

  NativeBridge(this.context, this._controller);

  
  // API mapping table with specific functions that can be called via _functions[key](data)
  / / such as _functions [] "getValue" (null)
  get _functions => <String.Function> {"getValue": _getValue,
        "inputText": _inputText,
        "showSnackBar": _showSnackBar,
        "newWebView": _newWebView,
      }; 

  @override
  String get name => "nativeBridge"; // js via nativeBridge.postMessage(MSG); Call the flutter

  // Handle the JS request
  @override
  get onMessageReceived => (msg) async {
    		
    		// Convert the received string data to JSON
        Map<String.dynamic> message = json.decode(msg.message);
    
  			// Asynchronous because some API functions may be implemented asynchronously, such as inputText, waiting for the UI to respond
    		// Call the specific function based on the API field
        final data = await _functions[message["api"]](message["data"]);
    
    		// Organize the return package
    		final res = <String.dynamic> {"guid": message["guid"]."api": message["api"]."data": data
        }
    
    		/ / js function, window. JsBridge. ReceiveMessage
    		// Convert the data to a string
        _controller.then((v) => v.evaluateJavascript(
            "window.jsBridge.receiveMessage(${json.encode(res)})"));
      };
  
  
  Map<String.dynamic> _getValue(data) => {"value": 1};

  Future<Map<String.dynamic>> _inputText(data) async {
    String text = await showDialog(
        context: context,
        builder: (_) {
          final textController = TextEditingController();
          return AlertDialog(
            content: TextField(controller: textController),
            actions: <Widget>[
              FlatButton(
                  onPressed: () => Navigator.pop(context, textController.text),
                  child: Icon(Icons.done)),
            ],
          );
        });
    return {"text": text ?? ""};
  }

  Map<String.dynamic> _showSnackBar(data) {
    Scaffold.of(context)
        .showSnackBar(SnackBar(content: Text(data["text"]????"")));
    return null;
  }

  Map<String.dynamic> _newWebView(data) {
    Navigator.of(context)
        .push(MaterialPageRoute(builder: (_) => WebViewPage(url: data["url")));return null; }}Copy the code

Add to webView:


class _WebViewPageState extends State<WebViewPage> {
  final_controller = Completer<WebViewController>(); .@override
  Widget build(BuildContext context) {
    ...
    			WebView(
						...
          javascriptChannels: [NativeBridge(context, _controller.future)].toSet(),
          )
  }
}
Copy the code

js

I can’t write JS, and I omitted the guID generation. Here’s how it works:

If Js calls flutter and needs to return a value, a callback function is prepared to process the data. After the flutter has processed the data, the callback function is proactively called.

. In order to facilitate the flutter calls, encapsulates the window jsBridge. ReceiveMessage (MSG), the function according to guid and API field distribution to specific callback function.


      
<html>

<body>
    <p id="getValue"></p>
    <hr />
    <p id="inputText"></p>

    <script>
        var callbacks = {};
        window.jsBridge = {
          	// js calls flutter and saves the callbacks in the callbacks
            invoke: function (api, data, callback) {
                callbacks[api] = callback;
                nativeBridge.postMessage(JSON.stringify({
                    api: api,
                    data: data || {},
                }));
            },
          
          	// The flutter is called after the data is processed. The callbacks are pulled out according to the GUID and API
            receiveMessage: function (msg) {
                if (callbacks[msg.api]) {
                    callbacks[msg.api](msg); // Execute the call}}};window.jsBridge.invoke("getValue", {}, function (data) {
            document.getElementById("getValue").innerHTML = JSON.stringify(data);
        });

        window.jsBridge.invoke("inputText", {}, function (data) {
            document.getElementById("inputText").innerHTML = JSON.stringify(data);
        });

        window.jsBridge.invoke("showSnackBar", { text: "snackBar should show" }, null);

        window.jsBridge.invoke("newWebView", { url: "https://lzyprime.github.io" }, null);


    </script>

</body>

</html>
Copy the code

~ lambda. :

  • Again, I don’t know js, and this is just a simple demo, just to show you how it works, but it’s going to be more rigorous in practice. However, the approach is similar: JS calls the Flutter and saves the callback, and when the flutter is processed, proactively calls the callback to return the value

  • The complete code is in the repository, in one file for convenience