Browse Source

Add JavascriptChannel abstraction in dart code

ivanasen 6 years ago
parent
commit
bb0a0241a5

+ 8 - 13
example/lib/main.dart

@@ -9,7 +9,14 @@ const kAndroidUserAgent =
 
 String selectedUrl = 'https://damp-coast-35782.herokuapp.com';
 
-const List<String> jsChannels = ['Print'];
+// ignore: prefer_collection_literals
+final Set<JavascriptChannel> jsChannels = [
+  JavascriptChannel(
+      name: 'Print',
+      onMessageReceived: (JavascriptMessage message) {
+        print(message.message);
+      }),
+].toSet();
 
 void main() => runApp(MyApp());
 
@@ -102,8 +109,6 @@ class _MyHomePageState extends State<MyHomePage> {
 
   StreamSubscription<double> _onScrollXChanged;
 
-  StreamSubscription<JavascriptMessage> _onPostMessage;
-
   final _urlCtrl = TextEditingController(text: selectedUrl);
 
   final _codeCtrl = TextEditingController(text: 'window.navigator.userAgent');
@@ -184,15 +189,6 @@ class _MyHomePageState extends State<MyHomePage> {
         });
       }
     });
-
-    _onPostMessage =
-        flutterWebViewPlugin.onPostMessage.listen((JavascriptMessage message) {
-      if (mounted) {
-        setState(() {
-          _history.add('onPostMessage: ${message.channel} ${message.message}');
-        });
-      }
-    });
   }
 
   @override
@@ -205,7 +201,6 @@ class _MyHomePageState extends State<MyHomePage> {
     _onProgressChanged.cancel();
     _onScrollXChanged.cancel();
     _onScrollYChanged.cancel();
-    _onPostMessage.cancel();
 
     flutterWebViewPlugin.dispose();
 

+ 1 - 0
lib/flutter_webview_plugin.dart

@@ -1,5 +1,6 @@
 library flutter_webview_plugin;
 
 export 'src/base.dart';
+export 'src/javascript_channel.dart';
 export 'src/javascript_message.dart';
 export 'src/webview_scaffold.dart';

+ 48 - 8
lib/src/base.dart

@@ -3,6 +3,7 @@ import 'dart:ui';
 
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
+import 'package:flutter_webview_plugin/src/javascript_channel.dart';
 
 import 'javascript_message.dart';
 
@@ -35,6 +36,11 @@ class FlutterWebviewPlugin {
   final _onHttpError = StreamController<WebViewHttpError>.broadcast();
   final _onPostMessage = StreamController<JavascriptMessage>.broadcast();
 
+  final Map<String, JavascriptChannel> _javascriptChannels =
+      // ignoring warning as min SDK version doesn't support collection literals yet
+      // ignore: prefer_collection_literals
+      Map<String, JavascriptChannel>();
+
   Future<Null> _handleMessages(MethodCall call) async {
     switch (call.method) {
       case 'onBack':
@@ -67,9 +73,8 @@ class FlutterWebviewPlugin {
             WebViewHttpError(call.arguments['code'], call.arguments['url']));
         break;
       case 'javascriptChannelMessage':
-        final JavascriptMessage javascriptMessage = JavascriptMessage(
+        _handleJavascriptChannelMessage(
             call.arguments['channel'], call.arguments['message']);
-        _onPostMessage.add(javascriptMessage);
         break;
     }
   }
@@ -99,8 +104,6 @@ class FlutterWebviewPlugin {
 
   Stream<WebViewHttpError> get onHttpError => _onHttpError.stream;
 
-  Stream<JavascriptMessage> get onPostMessage => _onPostMessage.stream;
-
   /// Start the Webview with [url]
   /// - [headers] specify additional HTTP headers
   /// - [withJavascript] enable Javascript or not for the Webview
@@ -129,7 +132,7 @@ class FlutterWebviewPlugin {
   Future<Null> launch(
     String url, {
     Map<String, String> headers,
-    List<String> javascriptChannelNames,
+    Set<JavascriptChannel> javascriptChannels,
     bool withJavascript,
     bool clearCache,
     bool clearCookies,
@@ -178,10 +181,21 @@ class FlutterWebviewPlugin {
       args['headers'] = headers;
     }
 
-    if (javascriptChannelNames != null) {
-      args['javascriptChannelNames'] = javascriptChannelNames;
+    _assertJavascriptChannelNamesAreUnique(javascriptChannels);
+
+    if (javascriptChannels != null) {
+      javascriptChannels.forEach((channel) {
+        _javascriptChannels[channel.name] = channel;
+      });
+    } else {
+      if (_javascriptChannels.isNotEmpty) {
+        _javascriptChannels.clear();
+      }
     }
 
+    args['javascriptChannelNames'] =
+        _extractJavascriptChannelNames(javascriptChannels).toList();
+
     if (rect != null) {
       args['rect'] = {
         'left': rect.left,
@@ -201,7 +215,10 @@ class FlutterWebviewPlugin {
 
   /// Close the Webview
   /// Will trigger the [onDestroy] event
-  Future<Null> close() async => await _channel.invokeMethod('close');
+  Future<Null> close() async {
+    _javascriptChannels.clear();
+    await _channel.invokeMethod('close');
+  }
 
   /// Reloads the WebView.
   Future<Null> reload() async => await _channel.invokeMethod('reload');
@@ -273,6 +290,29 @@ class FlutterWebviewPlugin {
     };
     await _channel.invokeMethod('resize', args);
   }
+
+  Set<String> _extractJavascriptChannelNames(Set<JavascriptChannel> channels) {
+    final Set<String> channelNames = channels == null
+        // ignore: prefer_collection_literals
+        ? Set<String>()
+        : channels.map((JavascriptChannel channel) => channel.name).toSet();
+    return channelNames;
+  }
+
+  void _handleJavascriptChannelMessage(
+      final String channelName, final String message) {
+    _javascriptChannels[channelName]
+        .onMessageReceived(JavascriptMessage(message));
+  }
+
+  void _assertJavascriptChannelNamesAreUnique(
+      final Set<JavascriptChannel> channels) {
+    if (channels == null || channels.isEmpty) {
+      return;
+    }
+
+    assert(_extractJavascriptChannelNames(channels).length == channels.length);
+  }
 }
 
 class WebViewStateChanged {

+ 36 - 0
lib/src/javascript_channel.dart

@@ -0,0 +1,36 @@
+import 'package:meta/meta.dart';
+import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
+
+final RegExp _validChannelNames = RegExp('^[a-zA-Z_][a-zA-Z0-9]*\$');
+
+/// A named channel for receiving messaged from JavaScript code running inside a web view.
+class JavascriptChannel {
+  /// Constructs a Javascript channel.
+  ///
+  /// The parameters `name` and `onMessageReceived` must not be null.
+  JavascriptChannel({
+    @required this.name,
+    @required this.onMessageReceived,
+  })  : assert(name != null),
+        assert(onMessageReceived != null),
+        assert(_validChannelNames.hasMatch(name));
+
+  /// The channel's name.
+  ///
+  /// Passing this channel object as part of a [WebView.javascriptChannels] adds a channel object to
+  /// the Javascript window object's property named `name`.
+  ///
+  /// The name must start with a letter or underscore(_), followed by any combination of those
+  /// characters plus digits.
+  ///
+  /// Note that any JavaScript existing `window` property with this name will be overriden.
+  ///
+  /// See also [WebView.javascriptChannels] for more details on the channel registration mechanism.
+  final String name;
+
+  /// A callback that's invoked when a message is received through the channel.
+  final JavascriptMessageHandler onMessageReceived;
+}
+
+/// Callback type for handling messages sent from Javascript running in a web view.
+typedef void JavascriptMessageHandler(JavascriptMessage message);

+ 1 - 6
lib/src/javascript_message.dart

@@ -3,13 +3,8 @@
 class JavascriptMessage {
   /// Constructs a JavaScript message object.
   ///
-  /// The `channel` parameter must not be null.
   /// The `message` parameter must not be null.
-  const JavascriptMessage(this.channel, this.message)
-      : assert(message != null, channel != null);
-
-  /// The contents of the channel that was sent by the JavaScript code.
-  final String channel;
+  const JavascriptMessage(this.message) : assert(message != null);
 
   /// The contents of the message that was sent by the JavaScript code.
   final String message;

+ 6 - 3
lib/src/webview_scaffold.dart

@@ -3,6 +3,7 @@ import 'dart:async';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/rendering.dart';
+import 'package:flutter_webview_plugin/src/javascript_channel.dart';
 
 import 'base.dart';
 
@@ -42,7 +43,7 @@ class WebviewScaffold extends StatefulWidget {
   final PreferredSizeWidget appBar;
   final String url;
   final Map<String, String> headers;
-  final List<String> javascriptChannels;
+  final Set<JavascriptChannel> javascriptChannels;
   final bool withJavascript;
   final bool clearCache;
   final bool clearCookies;
@@ -86,7 +87,9 @@ class _WebviewScaffoldState extends State<WebviewScaffold> {
     webviewReference.close();
 
     _onBack = webviewReference.onBack.listen((_) async {
-      if (!mounted) return;
+      if (!mounted) {
+        return;
+      }
 
       // The willPop/pop pair here is equivalent to Navigator.maybePop(),
       // which is what's called from the flutter back button handler.
@@ -147,7 +150,7 @@ class _WebviewScaffoldState extends State<WebviewScaffold> {
             webviewReference.launch(
               widget.url,
               headers: widget.headers,
-              javascriptChannelNames: widget.javascriptChannels,
+              javascriptChannels: widget.javascriptChannels,
               withJavascript: widget.withJavascript,
               clearCache: widget.clearCache,
               clearCookies: widget.clearCookies,