48

I have an html file that I am loading in Flutter webview using flutter_webview_plugin. I am using evalJavascript to call function in my javascript code, meaning flutter(dart)->js. However, I also need some way to communicate back something to flutter(dart) layer, meaning js->flutter(dart).

I have tried using - webkit.messageHandlers.native - window.native to support both platforms(Android,iOS) checking if those are available in JS. But, those comes as undefined. Using following code to get instance of native handler in JS.

typeof webkit !== 'undefined' ? webkit.messageHandlers.native : 
window.native;

And even if I get that instance and post message using it, not sure how to handle it in flutter(dart) layer. I may need to use platform channels. Not sure, if I am in the right direction.

Is there any way through which I can do that? I have evaluated interactive_webview plugin. It works fine on Android. But, it has swift versioning issue and don't want to proceed further with that.

Any help would be appreciated.

live-love
  • 48,840
  • 22
  • 240
  • 204
shrad
  • 491
  • 1
  • 4
  • 4
  • Hi shrad. Welcome to StackOverflow! Can you please edit your question and rephrase it so that it shows what you have already tried and are currently stuck at? It would be easier for others to contribute if you include any error messages/log. etc. – Ahmad Dec 09 '18 at 05:30
  • Thanks Ahmad. I have edited my question. I hope it helps in identifying the issue I am facing. – shrad Dec 09 '18 at 05:50
  • @shrad: I would encourage you to select an answer as the accepted answer. – piccy May 01 '19 at 19:24
  • Did any one find a solution to get the message from javascript to Flutter. – Saty Sep 11 '19 at 15:16

7 Answers7

62

Here is an example of communication from Javascript code to flutter.

In Flutter build your WebView like :

WebView(
              initialUrl: url,
              javascriptMode: JavascriptMode.unrestricted,
              javascriptChannels: Set.from([
                JavascriptChannel(
                    name: 'Print',
                    onMessageReceived: (JavascriptMessage message) {
                      //This is where you receive message from 
                      //javascript code and handle in Flutter/Dart
                      //like here, the message is just being printed
                      //in Run/LogCat window of android studio
                      print(message.message);
                    })
              ]),
              onWebViewCreated: (WebViewController w) {
                webViewController = w;
              },
            )

and in Your HTMLfile:

<script type='text/javascript'>
    Print.postMessage('Hello World being called from Javascript code');
</script>

When you run this code, you shall be able to see log "Hello World being called from Javascript code" in the LogCat/Run window of android studio.

Suresh Kumar
  • 1,077
  • 1
  • 12
  • 21
  • 3
    I just test your script and it is not working: `Print is not defined`. – sooon May 15 '19 at 02:57
  • 3
    This is strange. Print is the name of your JavaScriptChannel. If you are using different name, you should use that name in your JavaScript. I hope you are calling the webpage in WebView. (cuz this shall not work in a mobile/desktop browser) – Suresh Kumar May 15 '19 at 06:34
  • What i did alternatively is create a function and put the `Print.postMessage` in the function. then call it with the intended event. But your script might work too, just that I don't like the idea that it has error when open the html. – sooon May 16 '19 at 02:22
  • `Print` is channel name, but i want get message from `window.postMessage`, how to get it? – Hưng Trịnh Oct 17 '19 at 03:40
  • @sooon what is the intended event? How did you do that? – JavaRunner Oct 30 '19 at 18:37
  • @HưngTrịnh then probabally you need to change the name in Dart file from JavascriptChannel( name: 'Print', ... to JavascriptChannel( name: 'window',... But I am not sure, if you should use standard Javascript variables as Channel Name or not. – Suresh Kumar Nov 18 '19 at 09:23
  • @sooon did you figure it out? – anoop4real Dec 14 '20 at 17:21
  • How do I send messages from Flutter side to Javascript? – Mateus Felipe Mar 25 '21 at 17:40
  • guys please if someone succeeded on this let me know .. am struggling for a week and can't fix the is not defined error – Ahmed Wagdi Nov 09 '22 at 11:12
  • Uncaught ReferenceError: Print is not defined at HTMLButtonElement.send_Data . I am getting above error – Krishna prasad Dec 15 '22 at 15:46
23

You can try my plugin flutter_inappbrowser (EDIT: it has been renamed to flutter_inappwebview) and use addJavaScriptHandler({@required String handlerName, @required JavaScriptHandlerCallback callback}) method (see more here).

An example is presented below. On Flutter side:

...

child: InAppWebView(
  initialFile: "assets/index.html",
  initialHeaders: {},
  initialOptions: InAppWebViewWidgetOptions(
    inAppWebViewOptions: InAppWebViewOptions(
        debuggingEnabled: true,
    )
  ),
  onWebViewCreated: (InAppWebViewController controller) {
    webView = controller;

    controller.addJavaScriptHandler(handlerName: "mySum", callback: (args) {
      // Here you receive all the arguments from the JavaScript side 
      // that is a List<dynamic>
      print("From the JavaScript side:");
      print(args);
      return args.reduce((curr, next) => curr + next);
    });
  },
  onLoadStart: (InAppWebViewController controller, String url) {

  },
  onLoadStop: (InAppWebViewController controller, String url) {

  },
  onConsoleMessage: (InAppWebViewController controller, ConsoleMessage consoleMessage) {
    print("console message: ${consoleMessage.message}");
  },
),

...

On JavaScript side (for example a local file assets/index.html inside the assets folder):

<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Flutter InAppBrowser</title>
        
        ...
        
    </head>
    <body>

        ...

        <script>
           // In order to call window.flutter_inappwebview.callHandler(handlerName <String>, ...args) 
           // properly, you need to wait and listen the JavaScript event flutterInAppWebViewPlatformReady. 
           // This event will be dispatched as soon as the platform (Android or iOS) is ready to handle the callHandler method. 
           window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
             // call flutter handler with name 'mySum' and pass one or more arguments
             window.flutter_inappwebview.callHandler('mySum', 12, 2, 50).then(function(result) {
               // get result from Flutter side. It will be the number 64.
               console.log(result);
             });
           });
        </script>
    </body>
</html>

On Android Studio logs you will get:

I/flutter (20436): From JavaScript side:
I/flutter (20436): [12, 2, 50]
I/flutter (20436): console message: 64
Lorenzo Pichilli
  • 2,896
  • 1
  • 27
  • 50
  • This action is not working on button click – Krishna prasad Dec 16 '22 at 04:08
  • Listening the "flutterInAppWebViewPlatformReady" event inside a click event will not work because the "flutterInAppWebViewPlatformReady" will be called only once, when the platform channel is ready. So, on a javascript event "click", probably, you can call "window.flutter_inappwebview.callHandler" method directly. – Lorenzo Pichilli Dec 16 '22 at 14:08
13

I want to tell you about how to send messages from flutter WebView to JS:

  1. In JS code you need to bind your function you need to fire to window
const function = () => alert('hello from JS');
window.function = function;
  1. In your code in WebView widget implementation you need to declare onWebViewCreated method like this
WebView(
  onWebViewCreated: (WebViewController controller) {},
  initialUrl: 'https://url.com',
  javascriptMode: JavascriptMode.unrestricted,
)
  1. In class widget declare var _webViewController;
class App extends State<MyApp> {
  final _webViewController;
}
  1. In onWebViewCreated write this code
onWebViewCreated: (WebViewController controller) {
    _webViewController = controller;
},

Then you can run code like this:

class App extends StatelessWidget {
  var _webViewController;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        body: WebView(
          onWebViewCreated: (WebViewController controller) {
            _webViewController = controller;
          },
          initialUrl: 'https://url.com',
          javascriptMode: JavascriptMode.unrestricted,
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            // When you click at this button youll run js code and youll see alert
            _webViewController
                .evaluateJavascript('window.function ()');
          },
          child: Icon(Icons.add),
          backgroundColor: Colors.green,
        ),
      ),
    );
  }
}

But what if we want to share this _webViewController instance to other widgets like drawer?
In this case I decided to implement Singleton pattern and store _webViewController instance in it.
So
Singleton class

class Singleton {
  WebViewController webViewController;

  static final Singleton _singleton = new Singleton._internal();

  static Singleton get instance => _singleton;

  factory Singleton(WebViewController webViewController) {
    _singleton.webViewController = webViewController;
    return _singleton;
  }

  Singleton._internal();
}

Then

onWebViewCreated: (WebViewController controller) {
  var singleton = new Singleton(controller);
},

And finally in our Drawer widget i.e. (here you can use whatever widget you want)

class EndDrawer extends StatelessWidget {
  final singleton = Singleton.instance;

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          SizedBox(
              width: 200,
              child: FlatButton(
                onPressed: () {
                  singleton.webViewController.evaluateJavascript('window.function()');
                  Navigator.pop(context); // exit drawer
                },
                child: Row(
                  children: <Widget>[
                    Icon(
                      Icons.exit_to_app,
                      color: Colors.redAccent,
                    ),
                    SizedBox(
                      width: 30,
                    ),
                    Text(
                      'Exit',
                      style: TextStyle(color: Colors.blueAccent, fontSize: 20),
                    ),
                  ],
                ),
              )),
        ],
      ),
    );
  }
}

If you want to receive messages from JS code to your flutter App you need:

  1. In your js code
window.CHANNEL_NAME.postMessage('Hello from JS');
  1. In your flutter code.
    When you're running JavascriptChannel(name: 'CHANNEL_NAME', ...)
    flutter bind to your window WebView new MessageChannel with name you wrote in constructor (in this case CHANNEL_NAME)
    so when we call window.CHANNEL_NAME.postMessage('Hello from JS'); we recieve a message we sent
WebView(
   javascriptChannels: [
     JavascriptChannel(name: 'CHANNEL_NAME', onMessageReceived: (message) {
       print(message.message);
       })
   ].toSet(),
  initialUrl: 'https://url.com',
)

So here we are.
I'm new in flutter code
So if you have another better experience about this you can write in comments to help other people!

Dmitriy
  • 157
  • 1
  • 2
  • 8
  • 2
    I've used the second part that talks about sending messages from JS cod to flutter,, but am getting this error `TypeError: Cannot read properties of undefined (reading 'postMessage')` when I use `window.CHANNEL_NAME.postMessage("sadsad")` – Ahmed Wagdi Nov 11 '22 at 19:06
4

Full code example of Javascript callbacks using package flutter_inappwebview:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';

Future main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(new MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> {
  InAppWebViewController _webViewController;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('InAppWebView Example'),
        ),
        body: Container(
            child: Column(children: <Widget>[
          Expanded(
            child: InAppWebView(
              initialData: InAppWebViewInitialData(data: """
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    </head>
    <body>
        <h1>JavaScript Handlers (Channels) TEST</h1>
        <button id='test' onclick="window.flutter_inappwebview.callHandler('testFunc');">Test</button>
        <button id='testargs' onclick="window.flutter_inappwebview.callHandler('testFuncArgs', 1);">Test with Args</button>
        <button id='testreturn' onclick="window.flutter_inappwebview.callHandler('testFuncReturn').then(function(result) { alert(result);});">Test Return</button>
    </body>
</html>
                  """),
              initialOptions: InAppWebViewGroupOptions(
                  crossPlatform: InAppWebViewOptions(
                debuggingEnabled: true,
              )),
              onWebViewCreated: (InAppWebViewController controller) {
                _webViewController = controller;

                _webViewController.addJavaScriptHandler(
                    handlerName: 'testFunc',
                    callback: (args) {
                      print(args);
                    });

                _webViewController.addJavaScriptHandler(
                    handlerName: 'testFuncArgs',
                    callback: (args) {
                      print(args);
                    });

                _webViewController.addJavaScriptHandler(
                    handlerName: 'testFuncReturn',
                    callback: (args) {
                      print(args);
                      return '2';
                    });
              },
              onConsoleMessage: (controller, consoleMessage) {
                print(consoleMessage);
              },
            ),
          ),
        ])),
      ),
    );
  }
}
live-love
  • 48,840
  • 22
  • 240
  • 204
4

There are two ways to communicate the answer:

First way From Flutter to the webview (javascript, react...)

From the flutter side (using a button or in a trigger method):

webViewController.evaluateJavascript('fromFlutter("pop")');

This fromFlutter will be the name of the method in your javascript, react, whatever and also you can send text, in this case "pop".

From the javascript side inside the html, in your body label:

<script type="text/javascript">
    function fromFlutter(data) {
     // Do something
     console.log("This is working now!!!");
    }

  </script>

Second way From your webview (javascript, react...) to Flutter

In your Webview attribute javascriptChannels you can add:

javascriptChannels: Set.from([
     JavascriptChannel(
        name: 'comunicationname',
        onMessageReceived: (JavascriptMessage message) async {
          // Here you can take message.message and use 
          // your string from webview
        },
    )
]),

From the webview using the same communication name "communicationname" (your can use another name in both places):

  window.communicationname.postMessage("native,,,pop,");
phrogg
  • 888
  • 1
  • 13
  • 28
Pedro Molina
  • 1,101
  • 12
  • 12
3

Flutter 3.0.5 webview_flutter: ^3.0.4 flutter_js: ^0.5.0+6

Another way to use JavascriptChannels is to tranfer data from the "App" to your Website.

Dart:

JavascriptChannel(

          name: 'getFCMToken',
          onMessageReceived: (JavascriptMessage message) async {
            //print(message.message);

            final token = (await FirebaseMessaging.instance.getToken())!;
            final script = "var appToken =\"${token }\"";
            _webViewController.runJavascript(script);

          },
        ),

html:

<script  type = "text/javascript">
    window.onload = getFCMToken.postMessage('');
</script>

or Dart(Trigger):

OnPageFinished: (url) async {
   try {
 final token = (await FirebaseMessaging.instance.getToken())!;     
 var javascript = "var appToken=\"${token.toString()}\"";
       } catch (_) {}
}

so in your website code you have a js var "appToken" wich you can use in PHP or whatever.

gungott
  • 231
  • 3
  • 9
1

If you are using webviewx plugin which support web,ios and android than this is how we can do two way communication.

I have webpage which has index.html and other js,and css pages which I want to display in webview and communicate between flutter and web app.

1. From flutter to js listener

 IconButton(
           icon: Icon(Icons.developer_mode),
           onPressed: () {
             webviewController
                 .evalRawJavascript('window.myFunction()',
                     inGlobalContext: false)
                 .then((value) => print(value));
           },
         )      

Note: myFunction is function defined in javascript or html page as below.

function myFunction() {
 alert("I am an alert box!");
 return 'working';
}

2. From js/html to flutter listener
In html/js add button with listener

function submitClick() {
 var data = document.getElementById('data').value;
 SubmitCallback(data) //defined in flutter
}

Now In flutter(add dartCallback):

 WebViewX(
         javascriptMode: JavascriptMode.unrestricted,
         initialContent: '<h2> Loading </h2>',
         initialSourceType: SourceType.HTML,
         onWebViewCreated: (controller) {
           webviewController = controller;
           _loadHtmlFromAssets();
           webviewController.addListener(() {});
         },
         dartCallBacks: {
           DartCallback(
             name: 'SubmitCallback',
             callBack: (msg) {
               ScaffoldMessenger.of(context).showSnackBar(
                   SnackBar(content: Text('Submitted $msg successfully')));
             },
           ),
         },
       )

PS. Happy Coding

Kunchok Tashi
  • 2,413
  • 1
  • 22
  • 30