This is the ninth day of my participation in the Gwen Challenge.More article challenges

So it’s all in one pot?

Very smooth solution to the iOS terminal, Swift and H5 interaction problem, the company thinks that you just solve the iOS ah, it is best to write a general WebView case, can meet the daily iOS and Android dual-end WebView load H5 debugging.

This isn’t a one-size-fits-all rhythm?

Since it is dual-end debugging, it is inevitable to use a cross-platform solution to write this shell. Due to the technical background, Flutter is the first choice, and the WebView plugin for Flutter is webview_flutter!

Not to mention, FOR this sudden need, I also read the official documents and codes of Webview_FLUTTER repeatedly, and wrote my own Demo to verify it.

A brief introduction to the webview_FLUTTER plugin


A Flutter plugin that provides a WebView widget.

On iOS the WebView widget is backed by a WKWebView; On Android the WebView widget is backed by a WebView.

A plugin that provides a WebView component for Flutter.

In iOS is based on native WKWebView, and in Android is based on system WebView.


As can be seen from this introduction, although this is a Flutter plug-in, it actually Bridges WebView components based on the iOS and Android platform system level. The advantage of this method is to use native Flutter components at each end. As long as the logic of the intermediate communication layer is consistent and intact, This flattens out the ability of H5 to communicate with Flutter in a two-ended WebView.

Use and explanation of Flutter and JS intermodulation

Let’s simplify the official example here.

import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; class WebViewExample extends StatefulWidget { @override _WebViewExampleState createState() => _WebViewExampleState(); } class _WebViewExampleState extends State<WebViewExample> { final Completer<WebViewController> _controller = Completer<WebViewController>(); @override void initState() { super.initState(); if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Flutter WebView example'), ), // We're using a Builder here so we have a context that is below the Scaffold // to allow calling Scaffold.of(context) so we can show a snackbar. body: Builder(builder: (BuildContext context) { return WebView( initialUrl: 'https://www.baidu.com', 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('https://www.youtube.com/')) { print('blocking navigation to $request}'); return NavigationDecision.prevent; } print('allowing navigation to $request'); return NavigationDecision.navigate; }, onPageStarted: (String url) { print('Page started loading: $url'); }, onPageFinished: (String url) async { final webViewController = await _controller.future; final callback = await webViewController.evaluateJavascript("alert('Hello world');" ); print(callback); }, gestureNavigationEnabled: true, ); })); } JavascriptChannel _toasterJavascriptChannel(BuildContext context) { return JavascriptChannel( name: 'SeasonCallback', onMessageReceived: (JavascriptMessage message) { // ignore: deprecated_member_use Scaffold.of(context).showSnackBar( SnackBar(content: Text(message.message)), ); }); }}Copy the code

The core part of this code is the following section, please have a detailed look at my notes, detailed and important!!

If you can’t read the code for webview_flutter, consider downloading the code for webview_flutter and looking at it a little bit differently from the previous two articles.

Return WebView(/// the page URL to load initialUrl: 'https://www.baidu.com', /// javascriptMode: JavascriptMode unrestricted, / / / WebView generates success, will be a webViewController callback, it is very important, behind will continue to say onWebViewCreated: (WebViewController WebViewController) {/// accept this controller_controller. complete(WebViewController); }, /// the webView load callback progress is equivalent to Swift on WKWebView estimatedProgress KVO onProgress: (int progress) { print("WebView is loading (progress : $progress%)"); }, /// javascriptChannels, this is equivalent to the collection of javascript methods that the Flutter side listens on, which is the same as the JS handle registered by the userContentController in Swift. <JavascriptChannel>{toasterjavascriptChannel (context),} DecidePolicyFor navigationAction: // decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) (NavigationRequest request) {/// If (request.url.startswith ('https://www.youtube.com/')) {print('blocking navigation to $request}'); return NavigationDecision.prevent; } print('allowing navigation to $request'); / / / other jump to allow the return NavigationDecision. Navigate. // func webView(_ webView:); // func webView(_ webView:) WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) OnPageStarted: (String URL) {print('Page started loading: $url'); }, /// func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) Method onPageFinished: (String URL) async {/// The Completer takes the webView controller final webViewController = await _controller.future; /// Run the JS method via webViewController, which calls a JS popover and asynchronously retrieves the JS callback, This callback may be empty final callback = await webViewController. EvaluateJavascript (" alert (" Hello world ");" ); print(callback); }, / / / support gestures sideslip gestureNavigationEnabled: true,);Copy the code

As you can see, if you understand the meaning of the WKWebView WKNavigationDelegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate delegate callback initialization function.

Toasterjavascriptchannel is instantiated if you register to listen on a JS method handle:

JavascriptChannel _toasterJavascriptChannel(BuildContext context) { return JavascriptChannel( /// Func add(_ scriptMessageHandler: WKScriptMessageHandler, name: String) func add(_ scriptMessageHandler: WKScriptMessageHandler, name: String) 'SeasonCallback', /// listen for the SeasonCallback callback to get JS parameters to the Flutter side, as in the Swift WKScriptMessageHandler proxy callback,  /// func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) /// Then JS can call the function on the Flutter side: (JavascriptMessage message) {/// the Flutter receives JS message, Scaffold. Of (context).showsnackbar (SnackBar(content: Text(message.message)),); }); }Copy the code

Recall the 3 requirements from the previous article. What should be written about them if they need to be fulfilled in Flutter? Understand the last article and understand the notes of this article, should not be a problem.

conclusion

  • Webview_flutter in a Flutter essentially calls the two-ended native WebView component, so the corresponding consistency is good. If you understand the intermodulation between native and JS, then the intermodulation between Flutter and JS can follow the same rules.

  • Multiple proxy callbacks in Swift are done with Callback in Flutter. This looks compact and, while it may not be easy to see in code, is more in line with the declarative programming style of Flutter and requires an effort to get used to.

Use of webview_flutter

Matters needing attention

You need to add configurations and permissions in the info.plist file on iOS:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
<key>io.flutter.embedded_views_preview</key>
<true/>
Copy the code

The Android side:

  • 1. Add minimum SDK support in android/app/build.gradle:
android {
    defaultConfig {
        minSdkVersion 19
    }
}
Copy the code
  • 2. Enable mixed view on android
import 'dart:io'; import 'package:webview_flutter/webview_flutter.dart'; class WebViewExample extends StatefulWidget { @override WebViewExampleState createState() => WebViewExampleState(); } class WebViewExampleState extends State<WebViewExample> { @override void initState() { super.initState(); // Enable hybrid composition. If (platform.isAndroid) webView. Platform = SurfaceAndroidWebView(); } @override Widget build(BuildContext context) { return WebView( initialUrl: 'https://flutter.cn', ); }}Copy the code
  • 3. Enable HTTP options and privacy rightsProject/android/app/SRC/main/AndroidManifest. The XMLFile to add
<! - need to be added in the application of the android: usesCleartextTraffic = "true", whether it can be used to clear transmission, the SSL - > < application android: label = "web_view" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true"> <! - authority according to the project need to configure the - > < USES - permission android: name = "android. Permission. INTERNET" / > < USES - the permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <! - the permissions of the CAMERA - > < USES - permission android: name = "android. Permission. CAMERA" / > <! - the microphone permissions - > < USES - permission android: name = "android. Permission. RECORD_AUDIO" / > <! --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <! -- sd card permission --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />Copy the code

Pit point

If you use webview_flutter to load H5 on Android and need to call the system camera or album, this JS will not be available, but it will be available on iOS.

Android WebView:

The reason for this problem is that the official Webview_flutter plugin does not adapt to this function.

Flutter WebView Android H5 failed to upload files.

If you want Android to have this ability, you need to add permissions at the same time, rewrite the implementation of the plug-in on the Android side.

Of course, there is another plugin flutter_webview_plugin that can implement Android upload, but this plugin flutter_webview_plugin is not as easy to use or as functional as webview_flutter.

I wish someone had taught me how to rewrite the webview_flutter plugin to support upload on Android. I did this and crashed…

Continue tomorrow

Tomorrow we will explain Swift, which is exactly the use and implementation of WebViewJavaScriptBridge in OC. Come on!