Trigger a JavaScript function on a WebView in Flutter - javascript

I have a webpage that I load on a WebViw in flutter and that webpage has a javascript function that I want to run with a parameter from flutter.
I wrote this uderneath, and it looks like it is running the alert function fine, but when I try to run the function that is defined on the webpage javascript it says it is undefined. Why could this be? Also, do you see any other mistakes on my code that I may be missing, despite it working?
class WebViewPageState extends State<WebViewPage> {
final String url;
final String title;
WebViewPageState(this.url, this.title);
final Completer<WebViewController> _controller = Completer<WebViewController>();
late WebViewController _mycontroller;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Column(children: [
Expanded(
child: WebView(
initialUrl: this.url,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webviewcontroller) {
_controller.complete(_mycontroller = webviewcontroller);
},
onPageFinished: (url){
print("Ok we loaded page");
setState(() {
_mycontroller.runJavascriptReturningResult('alert("Hello, World!")');
});
},
))
]));
}
}

runJavascriptReturningResult is trying to get a return value from your javascript and since it's an alert it can't find it. try using _mycontroller.runJavascript('alert("Hello, World!")');

Related

Unable to open tel, mailto, whatsapp links in a flutter webview?

I am a web developer and beginner to flutter. I created a Flutter web view application by watching a few tutorials and its working fine for iOS and Android. But when I click on '0123456789', mailto:'abc#gmail.com', WhatsApp link (https://wa.me/9712345678), it's going page Not Found. I want to open an external application for those conditions. How to handle this task in a flutter to support in iOS and Android? I used the flutter webview plugin to launch a url like:
And may i know where to add this following code to work ?
if (request.url.contains("mailto:")) {
_launchURL(request.url);
return NavigationDecision.prevent;
} else if (request.url.contains("tel:")) {
_launchURL(request.url);
return NavigationDecision.prevent;
} else if (request.url.contains("sms:")) {
_launchURL(request.url);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
import 'package:flutter_offline/flutter_offline.dart';
import 'package:location/location.dart';
import 'package:url_launcher/url_launcher.dart';
_launchURL(url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
void main() => runApp(MyApp());
Color btnColor = Color(0xff03a9f3);
Color bgColor = Color(0xffe9f4fc);
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Qavenue',
home: OfflineBuilder(
connectivityBuilder: (
BuildContext context,
ConnectivityResult connectivity,
Widget child,
) {
final bool connected = connectivity != ConnectivityResult.none;
return Container(
child: connected
? MyHomePage()
: Center(
child: Image.asset(
'assets/offline_blue.gif',
fit: BoxFit.cover,
width: 200.0,
),
),
color: bgColor,
);
},
child: MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String url = "https://example.xyz/";
final flutterWebviewPlugin = new FlutterWebviewPlugin();
StreamSubscription<WebViewStateChanged>
_onchanged; // here we checked the url state if it loaded or start Load or abort Load
#override
void initState() {
super.initState();
_onchanged =
flutterWebviewPlugin.onStateChanged.listen((WebViewStateChanged state) {
if (mounted) {
if (state.type == WebViewState.finishLoad) {
// if the full website page loaded`
print("loaded");
} else if (state.type == WebViewState.abortLoad) {
// if there is a problem with loading the url
print("there is a problem");
} else if (state.type == WebViewState.startLoad) {
// if the url started loading
print("start loading");
}
}
});
}
#override
void dispose() {
super.dispose();
flutterWebviewPlugin
.dispose(); // disposing the webview widget to avoid any leaks
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: WillPopScope(
onWillPop: () {
flutterWebviewPlugin.canGoBack().then((value) {
if (value) {
flutterWebviewPlugin.goBack();
} else {
exit(0);
}
});
},
child: WebviewScaffold(
url: url,
withJavascript: true,
withZoom: false,
hidden: true,
geolocationEnabled: true,
initialChild: Container(
color: Colors.white,
child: Center(
child: Image.asset('assets/icon/images/logo1.jpg'),
),
),
)),
);
}
}
One way to solve this is to import url_launcher_string.dart too. You will need it when you specify the links.
import 'package:url_launcher/url_launcher_string.dart';
Run flutter pub get and then inside your main app dart (e.g main.dart) you tell it how to handle the urls.
launchURL(url) async {
if (await canLaunchUrl(url)) {
await launchUrl(url);
} else {
throw 'Could not launch $url';
}
}
The above part of code could be anywhere. You could also put it inside another dart and import it. It'd be better of course if you put it inside your webview widget class and not just anywhere like you did, unless you need to use it somewhere else too.
Afterwards you specify inside the webview widget class how to handle the urls.
e.g inside the:
class WebViewApp extends StatefulWidget {
}
The 'NavigationDecision' part of code.
e.g:
NavigationDecision _interceptNavigation(NavigationRequest request) {
if (request.url.contains("mailto:")) {
launchUrlString('mailto:specify email address here');
return NavigationDecision.prevent;
} else if (request.url.contains("tel:")) {
launchUrlString('tel:specify telephone number here');
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
}
In the above part of code you define the "request" and then you tell it what to do with each request. So if you define 'NavigationRequest request' then you tell it what to do with each request. If you define 'NavigationRequest name' then you specify what to do with that name. e.g.
NavigationDecision _interceptNavigation(NavigationRequest name) {
if (name.url.contains("mailto:")) {
launchUrlString('mailto:specify email address here');
return NavigationDecision.prevent;
} else if (name.url.contains("tel:")) {
launchUrlString('tel:specify telephone number here');
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
}
You can add more urls of course by editing the name.url.contains line and adding more else if decisions. e.g. whatsapp, telegram, viber, and so on. Just like you did with the sms.
Do not forget to call for your navigation decision code when you need it.
Inside your override where you tell your webview what's the initial url, you call your navigation decision with the name you set to it. e.g:
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: WebView(
initialUrl: 'https:url', // Webview initial url here
javascriptMode: JavascriptMode.unrestricted, // Enable use of javascript
navigationDelegate: _interceptNavigation, // Call for your navigation decision
),
)
);
}
We named it '_interceptNavigation' so that's what we call for. And that's as far as understanding that part of code. I hope that helped you. I explained it step by step for beginners too.

Flutter - How to get a Javascript callback in WebView?

I am using a WebView to load a local HTML file that contains the callback -
function onReward(data){
console.log("onReward: " + data.earnedThisSession);
Survey.postMessage(data.earnedThisSession);
}
This callback gets triggered when a user completes an action, I am currently facing two problems
The data.earnedThisSession returned by the function differs from every user and I want to get this as a variable in my dart code to reward my user
My WebView dependency is not printing console messages in my debug console
This is my JavascriptChannel -
final Set<JavascriptChannel> jsChannels = [
JavascriptChannel(
name: 'Survey',
onMessageReceived: (JavascriptMessage message) {
print(message.message);
}),
].toSet();
This is my WebviewScaffold -
FutureBuilder<String>(
future: _loadLocalHTML(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Scaffold(
appBar: AppBar(),
body: WebView(
initialUrl: Uri.dataFromString(
snapshot.data,
mimeType: 'text/html',
encoding: Encoding.getByName('utf-8'),
).toString(),
javascriptChannels: jsChannels,
javascriptMode: JavascriptMode.unrestricted,
),
);
}
How do I use evalJavascript in this to fetch my reward data?
Changing Survey.postMessage() to window.Survey.postMessage() may work. Im not sure how to use evalJavascipt with WebviewScaffold, i'm using like this:
final _webView = new FlutterWebviewPlugin();
final Set<JavascriptChannel> jsChannels = [
JavascriptChannel(
name: 'Survey',
onMessageReceived: (JavascriptMessage message) {
print(message.message);
}),
].toSet();
_webView.launch(url, jsChannels);
_webView.onStateChanged.listen((event) {
if (event.type == WebViewState.finishLoad) {
_webView.evalJavascript('Your Js Code' +
'window.Survey.postMessage("Your Return Mes.")');
}
});

braintree into flutter web using JS got this error "options.selector or options.container must reference a valid DOM node"

Trying to implement Braintree payment gateway into flutter web.
Still no SDK for flutter web from Braintree. So trying to implement their javascript SDK.
Here is my js file
function payment(auth){
var button = document.querySelector('submit-button');
console.log(auth);
console.log(button);
braintree.dropin.create({
authorization: auth,
container: 'dropin-container'
}, function (createErr, instance) {
console.log(createErr);
console.log(instance);
button.addEventListener('click', function () {
instance.requestPaymentMethod(function (requestPaymentMethodErr, payload) {
// Submit payload.nonce to your server
return payload.nonce
});
});
});
}
Calling this js function from dart. Here is the complete dart code.
#JS()
library my_script;
import 'dart:html';
import 'dart:js_util';
import 'package:carbonbins/model/model.dart';
import 'package:carbonbins/pages/navigation.gr.dart';
import 'package:carbonbins/utils/image_helper.dart';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'package:js/js.dart';
import 'package:js/js.dart' as js;
#JS()
external void initBraintree(auth);
#JS()
external String payment(auth);
class PaymentPage extends StatefulWidget {
final UserModel userModel;
PaymentPage({#required this.userModel});
#override
_PaymentPageState createState() => _PaymentPageState();
}
class _PaymentPageState extends State<PaymentPage> {
String auth = "sandbox_.....";
void getButton() {
var htmlL = """<div id="checkout-message"></div>
<div id="dropin-container"></div>
<button id="submit-button">Submit payment</button>""";
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
'payment-container',
(int viewId) => DivElement()
..appendHtml(htmlL)
..style.border = 'none');
print(HtmlElementView(
viewType: "dropin-container",
));
}
void setupDropin() {
print(auth);
var status = payment(auth);
print("Status: $status");
}
#override
void initState() {
getButton();
setupDropin();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: <Widget>[
SizedBox(
height: 100,
),
Container(
width: 500.0,
height: 300.0,
child: HtmlElementView(
viewType: "payment-container",
),
)
],
),
),
);
}
When I run this code, I see only the submit button in the screen. Got this error from web console,
"options.selector or options.container must reference a valid DOM node."
How can I integrate the Braintree payment into the flutter web?
or any other international payment gateway that works in flutter web.
Technical Disclaimer: flutter-web is in beta and I would not recommend it to be used with any payment service. This might lead to critical issues and not advisable.
The HtmlElementView widget adds all its elements into shadowdom which is not directly accessible for the global javascript context. Check this issue here in github.
The solution would be to pass the DivElement reference to the external js function. For e.g. in your case
Create the div element out side the build method and hold a reference, like in initSate
DivElement paymentDiv;
#override
initState(){
// always call before rest of the logic.
super.initState();
var htmlL = """<div id="checkout-message"></div>
<div id="dropin-container"></div>
<button id="submit-button">Submit payment</button>""";
paymentDiv= DivElement()
..appendHtml(htmlL)
..style.border = 'none');
// remaining logic
}
Then in your build/other method pass this element for the registerViewFactory method.
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
'payment-container',
(int viewId) => paymentDiv;
Set you JS interop to accept dynamic parameter.
#JS()
external String payment(dynamic auth);
Rewrite you Javascript to directly work with this element reference. e.g
function payment(auth){
var button = auth;
// Remaining logic
}

Load Angular 5 component after gapi has been loaded

I am writting an angular 5 app using the CLI.
I am using gapi to retrieve user data in order to populate a form.
The client script is included in the index.html file :
<head>
<meta charset="utf-8">
<script src="https://apis.google.com/js/client.js"></script>
...
</head>
Here is my component with the call :
userProfileModel: UserProfileModel = new UserProfileModel();
ngOnInit() {
this.form = this.toFormGroup();
this.onFormChanges();
this.userService
.getUserById(this.userId)
.then(usr => {
this.mapModel(usr.result['profile']);
})
.then(() => {
this.form.patchValue(this.userProfileModel);
});
}
And the userService's method :
declare var gapi: any;
export class UserService {
getUserById(id: number) {
return gapi.client.request({
method: 'GET',
'path': this.constants['endpoint_user_getbyid'] + '/' + id,
'root': this.constants['api_url']
});
}
...
}
Problem is, gapi seems to do not be initialized right after my component has finished loading : I have to set a >500ms timeout to be able to use it, and it's ugly.
This code gives me the following error :
ERROR TypeError: Cannot read property 'request' of undefined
Please not :
1- I haven't installed anything with npm / yarn, I am simply using the script with the gapi var declaration.
2 - after every build, the code works without any error and populates my form the first time the page is loaded, then after one refresh, it fails everytime.
How can I tell angular to load client.js at startup before all the components ?
Thanks !
Thanks to #ADarnal I finally found a solution by reading this topic :
How to pass parameters rendered from backend to angular2 bootstrap method
I followed the same exact process described in computeiro's answer.
My equivalent of the "BackendRequestClass" class is a GapiService.
In this Gapi service, the load method allows me to load gapi before any other call is executed :
/* GapiService */
loadGapi() {
return new Promise((resolve, reject) => {
gapi.load('client', () => {
gapi.client.init({apiKey: 'AIzaSyD5Gl9...'}).then(() => {
resolve(gapi.client);
})
});
});
}
// Method used in any component
getUserById(id: number) {
return gapi.client.request({
method: 'GET',
'path': this.constants['endpoint_user_getbyid'] + '/' + id,
'root': this.constants['api_url']
});
}
Finally; in my component, i inject this gapiService and I am able to use client.request on component init !
ngOnInit() {
this.gapiService
.getUserById(5066549580791808)
.then(
...
});

Specified cast is not valid when invoking javascript function in webbrowser

I'm trying to invoke javascript function in webbrowser.
Website have separate file with javascript functions.
This is a part of website html file:
<div class="header">
<a class="buttonrg" onclick="$(this).hide();remove('56442741')"> Remove </a>
</div>
This is remove function from .js file:
function remove(id) {
$.ajax({
type: "POST",
url: "ajax/remove.php",
data: "remove=" + id
});
}
And I'm trying to call 'remove' function with this script in c#:
public void RemoveOffer(int _id)
{
try
{
webBrowser.Document.InvokeScript("remove", new object[] { _id.ToString() });
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
but always when I'm trying to call this script console is showing me an error: specified cast is not valid.
What could went wrong?
Change data: "remove=" + remove to data: { "remove": id }
Are you calling the InvokeScript() from a thread other than the one that created it? Doing this caused this exception to be thrown for me.
It's a strange error message given the circumstances, however I resolved this with an Invoke()
e.g.
private void RemoveOffer(int _id)
{
if (webBrowser.InvokeRequired)
{
webBrowser.Invoke(new Action(() => { RemoveOffer(_id); }));
return;
}
webBrowser.Document.InvokeScript("remove", new object[] { _id }); // Not sure if the .ToString() is required...
}

Categories