how to pass data from parent page to webview page in flutter? - javascript

i want to pass a String data to a webview page, is it possible to do it in flutter? because i know how to do it using html and javascript via contentWindow.postMessage() but i don't know how to implement it in flutter, thanks.
This is my code in flutter to show the webview
Container(
height:500,
child: EasyWebView(
onLoaded: () {
print('loaded');
},
src: webviewsrc,
),
),

I haven't tried using EasyWebView, but I tried webview_flutter and I can run a Javascript command using its controller.
In this example, I added a scroll listener which detects if page is scrolled to bottom most.
Widget _buildWebview(BuildContext context) {
WebViewController controller;
return WebView(
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) async {
webViewController.clearCache();
controller = webViewController;
await controller.loadUrl('https://pub.dev/packages/webview_flutter');
},
onPageFinished: (String _) async {
controller.evaluateJavascript('''
window.addEventListener('scroll', function(e) {
if ((Math.ceil(window.innerHeight + window.pageYOffset)) >= document.body.offsetHeight) {
ScrollCallback.postMessage('END OF PAGE!!!');
}
});
''');
},
javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: 'ScrollCallback',
onMessageReceived: (JavascriptMessage message) {
print(message.message);
}),
},
);
}

Related

Send message from background.js to popup

I want to implement FCM in my chrome extension.
At the mement after a lot of research I've found that the quick and best way to implement fcm is using the old API chrome.gcm. At the moment this solution seems working fine and when the extension is loaded I'm able to get an fcm token.
Now what I want to do is to pass the token to the popup that is powered by vue.js I'm trying with this code but without success.
background.js
const openPopup = () => {
chrome.windows.create({
type: 'popup',
height: 520,
width: 440,
url: chrome.runtime.getURL('popup.html')
})
}
const registeredToken = (registrationId) => {
console.log('FCM Token')
console.log(registrationId)
openPopup()
chrome.runtime.sendMessage({fcmToken: registrationId})
if( chrome.runtime.lastError ) {
console.log('error')
}
}
const notificationId = (id) => {
if(chrome.runtime.lastError) {
console.log(chrome.runtime.lastError)
}
console.log(id)
}
chrome.runtime.onInstalled.addListener( () => {
console.log('FCM extension installed')
})
chrome.action.onClicked.addListener( (tab) => {
console.log(tab)
openPopup()
})
chrome.gcm.register(['my_sender_id'], registeredToken)
chrome.gcm.onMessage.addListener( (message) => {
console.log(message, message.data["gcm.notification.title"])
chrome.notifications.create('', {
type: 'basic',
iconUrl: 'letter.png',
title: message.data["gcm.notification.title"],
message: message.data["gcm.notification.body"],
buttons: [
{ title: 'Dismiss' },
{ title: 'Reply' }
]
}, notificationId)
})
chrome.notifications.onButtonClicked.addListener( (notificationId, buttonIndex) => {
console.log('button clicked')
console.log(notificationId, buttonIndex)
})
popup.vue file
<template>
<div class="main_app">
<h1>Hello {{msg}}</h1>
</div>
</template>
<script>
export default {
name: 'popupView',
data () {
return {
msg: ''
}
},
mounted() {
chrome.runtime.onMessage( (message, sender, sendResponse) => {
console.log(message, sender, sendResponse)
this.msg = message
})
},
methods: {
}
}
</script>
What I've noticed is that the chrome.runtime.sendMessage({fcmToken: registrationId}) will not work and on the popup side I'm unable to send or get messages from background
How I can pass messages between the vue.js powered popup and the background.js file of the extension?
Is better to use firebase client library to get push messages or the gcm is fine for this scope?
You can use the chrome.tabs.query and chrome.tabs.sendMessage APIs to send a message from the background to the Popup.
chrome.tabs.query({}, function (tabs) {
tabs.forEach((tab) => {
chrome.tabs.sendMessage(
tab.id,
youtPayload,
function (response) {
// do something here if you want
}
);
});
});
That's it!
I spend lots of hours to finding solution to the same proble and still not find any.
My current understanding is, that we are trying to do and use method for the purpose, they wasnt ment to be used for.
Key information leading to this:
popup.js can share the same. Js file and objects with background.js
documentation primarely is talking about passing data between web page (content.js) and others (popup.js or background.js)

Is there any way to communicate between Flutter and Javascript continuously on scrolling or other actions performed in Webview Flutter?

In my Flutter project, I am using WebView to load one html page which includes some Javascript functions. I have created some functions in Javascript file to communicate with Flutter and I call those functions from my Flutter main.dart class like below:
body: FutureBuilder<String>(
future: _initServer,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (!snapshot.hasData) {
// while data is loading:
return Center(
child: CircularProgressIndicator(),
);
} else {
final url = snapshot.data;
return WebView(
initialUrl: url,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
_controller2 = webViewController;
},
onProgress: (int progress) {
showAlert(context, _controller2!);
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) {
print('Page finished loading: $url');
_toasterJavascriptChannel(context);
},
gestureNavigationEnabled: true,
backgroundColor: const Color(0x00000000),
);
}
}),
),
);
}
JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
return JavascriptChannel(
name: 'flutterChannel',
onMessageReceived: (JavascriptMessage message) {
// ignore: deprecated_member_use
print('###MSG: ${message.message}');
});
}
void showAlert(BuildContext context, WebViewController controller) async{
await controller.runJavascript('window.AppComponent.makeAlert("$sendMsg")');
}
The showAlert function is called perfectly and Javascript message receive through channel also works.
But The problem is- these functions are called only once the webview is loaded. But I need a solution to communicate continuously with the JS (for example, if the user scrolls or highlight) to track every event performed by the user.

evaluate javascript takes too long in flutter webview?

I am trying to build a flutter app and I am trying to show up website in a webview I am using "flutter_webview_plugin: ^0.3.11" now I am trying to evaluate some javascript in order to delete some items before displaying the web page here is the code of the webview:
#override
Widget build(BuildContext context) {
return WebviewScaffold(
url: 'https://www.amazon.com',
appBar: AppBar(
title: Text('Amazon store'),
),
initialChild: WebviewAnimation(),
withJavascript: true,
);
and here is the code of evaluating I am listening to onStateChanged like below:
void initState() {
super.initState();
_onUrlChanged = flutterWebViewPlugin.onUrlChanged.listen((event) {
this.url = event;
});
_onStateChanged =
flutterWebViewPlugin.onStateChanged.listen((WebViewStateChanged state) {
if (mounted) {
setState(() {
flutterWebViewPlugin.hide();
var kiki = flutterWebViewPlugin.evalJavascript(
"document.getElementsByClassName(\"a-button-stack\")[0].style.display='none';");
_history.add('onStateChanged: ${state.type} ${state.url}');
kiki.then((value) {
setState(() {
flutterWebViewPlugin.show();
});
});
});
}
});
by doing this I am able to evaluate js, the problem is it takes too long to evaluate, therefore I want to show up the page once it has been evaluated...any ideas ...thanks in advance

Flutter Webview two way communication with Javascript

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.
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.
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
I want to tell you about how to send messages from flutter WebView to JS:
In JS code you need to bind your function you need to fire to window
const function = () => alert('hello from JS');
window.function = function;
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,
)
In class widget declare var _webViewController;
class App extends State<MyApp> {
final _webViewController;
}
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:
In your js code
window.CHANNEL_NAME.postMessage('Hello from JS');
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!
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);
},
),
),
])),
),
);
}
}
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,");
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.
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

jsdom not triggering internal <script>

I am trying to sample a page that has a script on it that changes the CSS of certain elements such that an attribute toggles between "active" and "inactive" based on the width of the window.
I have written nodeJS code that gathers and analyzes the page, but I cannot seem to trigger, or detect the triggering of the script. I suspect it has to do with defaultDocumentFeatures, but I could be wrong.
The script opens the page in JSDOM with a default width, then changes it to a specified block of other widths. This should result in changes in the output, but it does not. I am getting the same results for all situations. I suspect that the script on the page simply isn't running, but need help to make it do so
Here is my code (expurgated for public viewing.)
var express = require('express');
var router = express.Router();
var jsdom=require('jsdom');
router.get('/getSamplePage', function(req, res) {
getaEpicPage(req, res, function(contents){
console.log("got an sample page"+contents+"000000000");
//contents gets replaced by the actual results that will be processed upstream
res.send({'msg':'', 'contents':contents});
});
});
var getaSamplePage=function (req, res, callback) {
jsdom.defaultDocumentFeatures={
FetchExternalResources : ['script', 'css'],
ProcessExternalResources : ['script', 'css'],
MutationEvents : '2.0',
QuerySelector : false
};
var elementLocations=[
'sample_01',
'sample_02',
'sample_03'];
var contents=[{label:'DIV ID', value:'Is Populated', width: "Screen Width", position:"element size"}];
var windowWidths=[
479,
481,
781,
783,
1023,
1025,
]
for (var i in windowWidths){
jsdom.env({
url:'http://sourcefilelocation/',
scripts: ['http://code.jquery.com/jquery.js'],
created: function(errors, tstWindow) {
tstWindow.tstWindowWidth=windowWidths.pop();
tstWindow.addEventListener('resize', function() {
//verify that resize is triggered
console.log('Resize event completed');
});
tstWindow.constructor.prototype.resize = function (width){
//defining a "resize" event, since that may be what triggers things
console.log("resize has been attempted");
tstWindow.originalWidth=tstWindow.innerWidth;
tstWindow.outerWidth=tstWindow.innerWidth=width;
}
tstWindow.readyState="complete";
},
done: function(errors, tstWindow) {
setTimeout(function () {
//setting a timeout to ensure that any elements have finished I have put this as high as ten seconds.
console.log("ready state "+tstWindow.readyState);
tstWindow.resize(tstWindow.tstWindowWidth)
$=tstWindow.$;
for (var sampleLocation in sampleLocations) {
var sampleID=sampleLocations[sampleLocation];
$('div > [sampleAttribute='+sampleID+']').each(function(){
//If the script I am trying to watch work triggers, it should change the "content" attribute
var elementActive=$(this).css('content');
var position=$(this).attr('sample-position');
console.log("element css>>>>>> "+tstWindow.innerWidth+" "+sampleID+" "+position+" "+elementActive);
if (elementActive=='"active"'){
contents.push({label:sampleID, value: elementActive, width: tstWindow.originalWidth+"/"+tstWindow.innerWidth, position:position});
}
});
};
}, 50);
}
});
};
setTimeout(function () { callback(contents);}, 100);
};
module.exports = router;
Per suggestion I added this to my jsDom config object, just after the url:
FetchExternalResources : ['script', 'css'],
ProcessExternalResources : ['script', 'css'],
MutationEvents : '2.0',
QuerySelector : false,
But it has made no apparent difference.
As per the jsdom Readme when you're using jsdom.env the default feature set does not include processing scripts.
You have to pass the FetchExternalResources and ProcessExternalResources to jsdom.env specifically.
jsdom.env({
html: input,
url: url,
features: {
FetchExternalResources: ["script", "img", "css", "frame", "iframe", "link"],
ProcessExternalResources: ["script"]
},
created: function (err, window) {
console.log('Created');
},
loaded: function (err, window) {
console.log('Loaded');
}
});
jsdom.env doesn't use the jsdom.defaultDocumentFeatures object.

Categories

Resources