Dart: Load an html file into a Flutter Webview - javascript

Sthg makes me crazy, I try to write an html file on the disk using the path_provider plugin with the dart:io library.
Here is what I tried (OKAY):
Future<File> writeFile() async {
final file = await _localFile;
return file.writeAsString('<!DOCTYPE html><html lang="fr"> <head> <meta charset="UTF-8"> <title>test_design</title> ...');
}
then the file is loaded in a webview: OKAY
But if I try with a longer html file, it doesn't work anymore, eg :
return file.writeAsString(' html file containing js ');
Any idea?
or how to load an html file (the file is not static) in a Flutter webview ?
I use flutter_webview_plugin.dart
(https://pub.dartlang.org/packages/flutter_webview_plugin)
webview code :
writeFile().then((file){
readFile().then((r){
_localPath.then((path){
var uri='file:///'+path+'/index.html';
flutterWebViewPlugin.launch(uri,
rect: new Rect.fromLTWH(
0.0,
400.0,
MediaQuery.of(context).size.width,
200.0,
),
);

IMO, this is the approach for this:
This is an example approach, I will be building a Flutter app that can read file and write data to file for later use.
It is an app that could write the String to text.txt file. Everytime the app is launched it will display the contents of text.txt.
I need a place to write data on disk and read it again when the app loads. So I used path_provider plugin to access Documents directory (on iOS, this corresponds to NSDocumentDirectory, on Android, this is the AppData directory).
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Create a reference to the File full location (in our case, the text.txt file), we use File class from the dart:io library.
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/text.txt');
}
Need to write a string to a file using File writeAsString() method. It returns a Future<File> that completes with this File object once the entire operation has completed.
By default, writeAsString() creates the file and truncates the file if it already exists.
To append data to existing file, pass FileMode.append mode as second parameter.
Future<File> writeFile(String text) async {
final file = await _localFile;
return file.writeAsString('$text\r\n', mode: FileMode.append);
}
Use File readAsString() method to read the entire contents as a string. It returns a Future<String> that completes with the string once contents has been read.
Future<String> readFile() async {
try {
final file = await _localFile;
String content = await file.readAsString();
return content;
} catch (e) {
return '';
}
}
Here is the complete sample code:
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'dart:io';
void main() {
runApp(
MaterialApp(
title: 'Read/Write Files',
home: MyApp(storage: TextStorage()),
),
);
}
class TextStorage {
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/text.txt');
}
Future<String> readFile() async {
try {
final file = await _localFile;
String content = await file.readAsString();
return content;
} catch (e) {
return '';
}
}
Future<File> writeFile(String text) async {
final file = await _localFile;
return file.writeAsString('$text\r\n', mode: FileMode.append);
}
Future<File> cleanFile() async {
final file = await _localFile;
return file.writeAsString('');
}
}
class MyApp extends StatefulWidget {
final TextStorage storage;
MyApp({Key key, #required this.storage}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
TextEditingController _textField = new TextEditingController();
String _content = '';
#override
void initState() {
super.initState();
widget.storage.readFile().then((String text) {
setState(() {
_content = text;
});
});
}
Future<File> _writeStringToTextFile(String text) async {
setState(() {
_content += text + '\r\n';
});
return widget.storage.writeFile(text);
}
Future<File> _clearContentsInTextFile() async {
setState(() {
_content = '';
});
return widget.storage.cleanFile();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Read/Write File Example'),
backgroundColor: Colors.blue,
),
body: Container(
padding: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
TextField(
controller: _textField,
),
Padding(
padding: EdgeInsets.all(20.0),
child: RaisedButton(
child: Text('Write to File'),
color: Colors.lightBlueAccent,
onPressed: () {
if (_textField.text.isNotEmpty) {
_writeStringToTextFile(_textField.text);
_textField.clear();
}
},
),
),
Padding(
padding: EdgeInsets.only(bottom: 20.0),
child: RaisedButton(
child: Text(
'Clear Contents',
style: TextStyle(color: Colors.white),
),
color: Colors.grey,
onPressed: () {
_clearContentsInTextFile();
},
),
),
Expanded(
flex: 1,
child: new SingleChildScrollView(
child: Text(
'$_content',
style: TextStyle(
color: Colors.blue,
fontSize: 22.0,
),
),
),
),
],
),
),
);
}
}
Here's how it looks when run:
You can play around depending on your requirement.
Also for your other question on how to load an html file in Flutter webview, here is my approach:
You can actually make it work using the webview_flutter plugin.
Create an assets folder in the root directory of your Flutter application, then put your .html file in the assets folder.
You should add your .html file to under assets in your pubspec.yaml file.
pubspec.yaml
assets:
- assets/SampleHtml.html
Here is a sample implementation to load an html from a local file:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:async';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<String> localLoader() async {
return await rootBundle.loadString('assets/SampleHtml.html');
}
#override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: localLoader(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return WebView(
initialUrl:
new Uri.dataFromString(snapshot.data, mimeType: 'text/html')
.toString(),
javascriptMode: JavascriptMode.unrestricted,
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
});
}
}
SampleHtml.html
<!DOCTYPE html>
<meta charset="utf-8">
<title>Sample HTML</title>
<html>
<head>
<link rel="stylesheet">
</head>
<body>
<h1>Flutter Webview</h1>
<p>A Flutter plugin that provides a WebView widget.</p>
<ul>
<li>List 1</li>
<li>List 2</li>
<li>List 3</li>
</ul>
</body>
</html>
This is how it looks like:

Related

how can I remove header and footer in Flutter WebView?

This is what I used
There's this video by Joannes Mike on YouTube I followed on how to remove header and footer in Flutter WebView but it seems flutter had upgraded her library and this functions don't work anymore.
Been at this for days and it seems no recent info is available.
Heres my code
import 'package:sportybet_betting_tips/main.dart';
import 'package:webview_flutter/webview_flutter.dart';
class sportybet_tips extends StatefulWidget {
const sportybet_tips({super.key});
#override
State<sportybet_tips> createState() => _sportybet_tipsState();
}
class _sportybet_tipsState extends State<sportybet_tips>
{
late final WebViewController controller;
#override
void initState(){
super.initState();
controller = WebViewController()
..loadRequest(Uri.parse('https://accessbettingtips.com/sportybet/'),);
// controller.addJavaScriptChannel(
// "document.getElementsByTagName('header')[0].style.display='none'", onMessageReceived: (JavaScriptMessage ) { });
// controller.loadRequest(Uri.parse('http://accessbettingtips.com'),);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sportybet Betting Tips'),
),
body: WebViewWidget(controller: controller),
// controller.evaluateJavascript(
// "document.getElementsByTagName('header')[0].style.display='none'");
// controller.loadRequest(Uri.parse('http://accessbettingtips.com'),);
);
}
// webview_flutter({required JavaScriptMode JavaScriptMode, required String initialUrl}) {}
}
in your onPageFinished method write like that :
onPageFinished: (url) async {
controller.runJavascript("document.getElementsByTagName('header')[0].style.display='none'");
},

Single HTTP request for HTML template file for multiple instances of the same Web Component

Say I have a Web Component defined like this:
// web-component.js
export class WebComponent extends HTMLElement {
template = '/path/to/template.html';
tmpl = {};
constructor() {
super();
}
async connectedCallback() {
const html = fetch(this.template).then(response => response.text());
this.doSomething(await html);
}
doSomething(html) {
console.log(html);
}
}
document.addEventListener('DOMContentLoaded', customElements.define('web-component', WebComponent));
A template file like this:
//template.html
<template id="web-component">
<h1>Text Goes Here</h1>
</template>
And a web page like this:
//index.html
....
<head>
<script type="module" src="/path/to/web-component.js"></script>
</head>
<body>
<web-component>Foo</web-component>
<web-component>Bar</web-component>
<web-component>Baz</web-component>
</body>
....
The web browser is making three http requests to fetch the same template file. I want to store the html from the template file somewhere on the client so I'm only making a single http request.
I tried this:
async connectedCallback() {
const existing_template = document.body.querySelector('#web-component');
console.log(existing_template);
if (existing_template) {
this.doSomething(existing_template);
} else {
const html = fetch(this.template).then(response => response.text());
this.addTemplateToWebPage(await html);
this.doSomething(await html);
}
addTemplateToWebPage(html) {
const tag = document.createElement('body');
tag.innerHTML = html;
document.body.appendChild(tag.querySelector('template'));
}
But existing_template is always null, so I'm still making unnecessary http requests. I also tried this, with the same result:
connectedCallback() {
this.doSomething();
}
async doSomething() {
const existing_template = document.body.querySelector('#web-component');
console.log(existing_template);
if (existing_template) {
this.doSomethingElse(existing_template);
} else {
const html = fetch(this.template).then(response => response.text());
this.addTemplateToWebPage(await html);
this.doSomethingElse(await html);
}
}
doSomethingElse(html) {
console.log('doing something else');
}
How can I do this so I only have a single http request when calling the same template?
What you're doing to the HTML isn't clear, but an easy way is to create a custom fetch wrapper for your HTML template. Something like this:
const fetchTemplate = (() => {
const cache = new Map();
return async (path, options) => {
if(cache.has(path)) return cache.get(path);
const res = await fetch(path, options);
const data = await res.text();
cache.set(path, data);
return data;
};
})();
This presents a much more streamlined approach and can be easily adapted for other templates.
Usage:
async connectedCallback() {
const html = await fetchTemplate(this.template);
this.doSomething(html);
}
Since the original question refers to an "HTML" template, I'm not sure if, technically, this really answers it, but it has been a few days so I will relate how I solved my problem. Basically I just changed template.html to template.js, turned the template html code into a javascript literal, and export/imported it into my class. I guess the browser automatically caches the http request because it's only making a single http call. I didn't transpile or use any build tools ... it just worked in the most recent versions of Firefox, Chrome and Edge (which is all I'm concerned about). It has been awhile since I have played with this stuff and I'm kinda blown away at the current browser support for vanilla javasript modules. When we finally get HTML imports that play nice with javascript modules, I'm hoping this approach makes it easy to refactor the code to accommodate the new technology.
My file structure is:
/www
- index.html
- /components
-- /my-component
--- component.js
--- template.js
index.html
<html>
<head>
<script type="module" src="/components/my-component/component.js"> </script>
</head>
<body>
<my-component>One</my-component>
<my-component>Two</my-component>
<my-component>Three</my-component>
</body>
</html>
template.js
export const template = `
<template id="my-template">
<style>
h1 { color: dimgray }
</style>
<div id="wrapper">
<h1>Need Text</h1>
</div>
</template>
`;
component.js
import { template } from './template.js';
export class MyComponent extends HTMLElement {
shadow = {};
tmpl = {};
constructor() {
super();
this.shadow = this.attachShadow({mode:'open'});
}
connectedCallback() {
this.setVars();
this.build();
this.render();
}
setVars() {
const tmpl = this.setTemplate(template);
this.tmpl = tmpl.querySelector('template').content;
}
setTemplate(html) {
const range = document.createRange();
return range.createContextualFragment(html);
}
build() {
// do something
}
render() {
this.innerHTML = "";
this.shadow.append(this.tmpl);
}
}
document.addEventListener('DOMContentLoaded', () => customElements.define('my-component', MyComponent));
Add a static class property, initialized as FALSE
In the class constructor, check the value of the static property and, if needed, fetch the template file.
In the constructor, set the value of the static property with the result of the fetch.
Flag the connectedCallback method as async and use AWAIT to retrieve the
static class property.
Clone the template to use in the instance(s).
my-component.js
class MyComponent extends HTMLElement {
static file = '/path/to/template.html';
static template = false;
constructor() {
super();
if (!MyComponent.template) {
MyComponent.template = MyComponent.fetchTemplate();
}
}
async connectedCallback() {
const tmpl = await MyComponent.template;
this.tmpl = tmpl.cloneNode(true);
console.log(this.tmpl);
}
static async fetchTemplate() {
return await fetch (MyComponent.file)
.then (response => response.text())
.then (txt => {
const frag = document.createRange().createContextualFragment(txt);
return frag.querySelector('template').content;
});
}
}
template.html
<template id="my-component">
<h1>Text Goes Here</h1>
</template>

How to import js library properly in dart? Flutter web application

I want to render a YooKassa widget in flutter app, so I've written this code to implement it
https://yookassa.ru/checkout-widget/v1/checkout-widget.js
#JS()
library checkout-widget.js;
import 'package:js/js.dart';
#JS('YooMoneyCheckoutWidget')
class YooMoneyCheckoutWidget {
external YooMoneyCheckoutWidget(YooMoneyCheckoutWidgetParams params);
external render(String viewID);
}
#JS()
#anonymous
class YooMoneyCheckoutWidgetParams {
external factory YooMoneyCheckoutWidgetParams({
String confirmation_token,
String return_url,
Map<String, dynamic> customization,
Function error_callback
});
}
class YooKassa {
YooMoneyCheckoutWidget _checkoutWidget;
YooKassa({String confirmation_token,String return_url}):_checkoutWidget = YooMoneyCheckoutWidget(YooMoneyCheckoutWidgetParams(confirmation_token: confirmation_token, return_url: return_url, customization: {'colors': {'controlPrimary':'#00BF96'}},error_callback: null));
void render(String viewID) => _checkoutWidget.render(viewID);
}
then trying to call the constructor for a JS code
final widget = YooKassa(confirmation_token: 'ct-2834a83d-000f-5000-9000-1316627a4610',return_url: 'https://app.moneyhat.ru/');
and then render it in DivElement in a HTMLElement
final wrapper = html.DivElement()
..style.width = '100%'
..style.height = '100%'
..id = 'pay-form';
ui.platformViewRegistry.registerViewFactory(
viewID,
(int viewId) => wrapper,
);
widget.render('pay-form');
return Scaffold(
body: SizedBox(
height: 500,
child: HtmlElementView(
viewType: viewID,
),
));
}
What's wrong here? Flutter raises an error - YooMoneyCheckoutWidget is not a constuctor

Use js file inside the html file Flutter

How can I use a js file associated with an html in Flutter. I use the webview_flutter plug-in to load the index.html file and it works, but I am not able to load the js file
This is my Flutter code:
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: WebView(
initialUrl: '',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_webViewController = webViewController;
_loadHtmlFromAssets();
},
),
_loadHtmlFromAssets() async {
String fileHtmlContents = await rootBundle.loadString('files/index.html');
_webViewController.loadUrl(Uri.dataFromString(fileHtmlContents, mimeType: 'text/html', encoding: Encoding.getByName('utf-8')).toString());
}
And this is my html file
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript" **src="files/plotter.js**"></script>
<title>Test plotter</title>
</head>
<body>
<!-- <script type="text/javascript" src="plotter.js"></script> -->
<div id='test'></div>
<script type="text/javascript">
This is the message that appears in conosle
I/chromium(31201): [INFO:CONSOLE(86)] "Uncaught ReferenceError: Plotter is not defined", source: data:text/html;charset=utf-8,%
Instead of reading the asset and then loading from data uri, just pass the asset path like this:
_loadHtmlFromAssets() async {
_webViewController.loadUrl('file:///android_asset/flutter_assets/files/index.html');
}
And in index.html file, the script path should be relative. For me index.html and index.js were in same path so this works:
<script src="index.js"></script>
And my pubspec.yml looks like this:
assets:
- files/index.html
- files/index.js
Edit
Platform independent solution using local_assets_server:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:local_assets_server/local_assets_server.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) => MaterialApp(home: MyWidget());
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
String address;
int port;
bool isListening = false;
#override
initState() {
_initServer();
super.initState();
}
_initServer() async {
final server = new LocalAssetsServer(
address: InternetAddress.loopbackIPv4,
assetsBasePath: 'files',
);
final address = await server.serve();
setState(() {
this.address = address.address;
port = server.boundPort;
isListening = true;
});
}
List<String> propList = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My WebView'),
),
body: !isListening ? Center(child: CircularProgressIndicator()) : WebView(
initialUrl: 'http://$address:$port',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
}),
);
}
}
And for android, add usesCleartextTraffic="true" in manifest file:
<application
....
android:usesCleartextTraffic="true"

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

Categories

Resources