Flutter web: The communication between Flutter web and Flutter APP fails - flutter

APP : Developed with Flutter
Web: Develop with Flutter
Use the webview_flutter plugin to load web pages in app.
Now the web page wants to communicate with the APP.
It is possible to use JavaScript methods to interact with flutter.
JavaScript code
function toast() {
Toast.postMessage("message from web page");
}
Flutter APP code
JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
return JavascriptChannel(
name: 'Toast',
onMessageReceived: (JavascriptMessage message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
});
}
The above method is possible.
But now Flutter web tries to interact with Flutter APP and fails, code show as below
Flutter web code
void toast() {
html.window.postMessage("message from web page", "Toast");
}
Flutter APP code same as above.
The error message is as follows
I/chromium(25735): [INFO:CONSOLE(14560)] "SyntaxError: Failed to
execute 'postMessage' on 'Window': Invalid target origin 'Toast' in a
call to 'postMessage'."
Is there something wrong with my calling method?

There's a way, but it doesn't feel good.
Define a JavaScript file in the web directory, here called toast.js. This file defines the methods to communicate with Dart.
function makeToast(msg) {
Toast.postMessage(msg);
}
Import toast.js in index.html in the web directory
<head>
// ...
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
<!-- new line -->
<script src="toast.js"></script>
</head>
Go back to the Flutter project and create a dart file in the lib directory, called js_channel.dart here, declare a method in this file for Dart to call JavaScript methods
import 'package:js/js.dart';
#JS('makeToast')
external void makeToast(String msg);
Call makeToast method
void toast() {
makeToast("msg from flutter web")
}
The above steps are all done in the flutter web project.
Next, you need to use the webview in the Flutter native (android or ios) project to load the web page built by the Flutter web project, and then listen to the message sent by the Toast object in the webview.
Here I am using the webview_flutter plugin
Widget _buildWebView() {
return WebView(
debuggingEnabled: true,
initialUrl: "your web url",
javascriptMode: JavascriptMode.unrestricted,
javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: 'Toast',
onMessageReceived: (JavascriptMessage message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
},
),
},
),
}

Related

Flutter web application lauch twice (Bootstrap)

I am using firebase Hosting to deploy my flutter web app.
I wonder why my application lauch twice? Once with Auto Bootstrap and then Programmatic Bootstrap.
enter image description here
However in index.html the app is run once:
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
}
}).then(function(engineInitializer) {
return engineInitializer.initializeEngine();
}).then(function(appRunner) {
return appRunner.runApp();
});
});
</script>
I tryed to change the firebase configs. Same result.

Flutter webview PayPal intergation error?

I want to integrate PayPal buttons like this:
https://developer.paypal.com/demo/checkout/#/pattern/client
I put this sample code in a HTML file (assets/paypal.html), and read from assets into the WebView:
#override
Widget build(BuildContext context) {
return Scaffold(
body: WebView(
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller = webViewController;
_loadHtml();
},
),
);
}
void _loadHtml() async {
String fileText = await rootBundle.loadString('assets/paypal.html');
_controller!.loadUrl(Uri.dataFromString(fileText, mimeType: 'text/html', encoding: Encoding.getByName('utf-8')).toString());
}
But I get this error (Uncaught Error: Bootstrap Error for buttons):
[INFO:CONSOLE(2)] "insertPPTM [object Object]", source: https://www.paypal.com/sdk/js?client-id=test&currency=USD (2) [INFO:CONSOLE(2)] "Uncaught Error: Bootstrap Error for buttons:
Can not read window host
Error: Can not read window host
at j (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:18528)
at B (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:18625)
at zt (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:58764)
at Cu (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:288789)
at Object.__get__ (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:288874)
at o (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:291801)
at https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:291984
at Module.<anonymous> (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:292214)
at t (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:157)
at https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:941", source: https://www.paypal.com/sdk/js?client-id=test&currency=USD (2) [INFO:CONSOLE(2)] "Uncaught Error: Bootstrap Error for common:
Can not read window host
Error: Can not read window host
at j (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:18528)
at B (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:18625)
at zt (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:58764)
at Iu (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:289405)
at Object.__get__ (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:289453)
at o (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:291801)
at https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:291984
at Module.<anonymous> (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:292214)
at t (https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:157)
at https://www.paypal.com/sdk/js?client-id=test&currency=USD:2:941", source: https://www.paypal.com/sdk/js?client-id=test&currency=USD (2) [INFO:CONSOLE(2)] "unhandled_error [object Object]", source: https://www.paypal.com/sdk/js?client-id=test&currency=USD (2) [INFO:CONSOLE(2)] "unhandled_error [object Object]", source: https://www.paypal.com/sdk/js?client-id=test&currency=USD (2) [INFO:CONSOLE(2)] "Uncaught Error: Can not read window host", source: https://www.paypal.com/sdk/js?client-id=test&currency=USD (2) [INFO:CONSOLE(2)] "Uncaught Error: Can not read window host", source: https://www.paypal.com/sdk/js?client-id=test&currency=USD (2)
I tried 2 flutter WebView plugins, same error ( webview_flutter 2.0.10 , flutter_webview_plugin 0.4.0 )
PayPal cannot be used from webviews, as the documentation explains.
Do not use a WebView to display PayPal web pages within your
application
Your application must not use a WebView or similar custom browser
mechanism for display of PayPal web pages. Instead, use an appropriate
PayPal SDK to manage the PayPal experience or launch the PayPal web
page within the system browser or an approved browser-view mechanism
such as Safari View Controller on iOS or Chrome Custom Tabs on
Android.

How to fix 'App not found' on iOS (AppAvailability)

I am launching a flutter app (App 1) from another flutter app (App 2) on Android and it works perfectly. However, I keep getting the same error on IOS. It does not recognize the package name.
I have tried with multiple apps but is the same result, it currently just recognizes the calendar package.
pubspec.yaml file flutter_appavailability
main.dart App 2
import 'package:flutter_appavailability/flutter_appavailability.dart';
//Future void method
if (Platform.isIOS) {
_installedApps = [
{"app_name": "example", "package_name": "com.flutter.example"},
];
print(await AppAvailability.checkAvailability("com.flutter.example")); //ERROR
//Widget build
return Container {
child : Row(
...
GestureDetector(
...
onTap: () {
Scaffold.of(context).hideCurrentSnackBar();
AppAvailability.launchApp(installedApps[0].["package_name"])
.then((_) {
print("App ${installedApps[0]["app_name"]} launched!");
}).catchError((err) {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
"App ${installedApps[0]["app_name"]} not found!")));
print(err);
});
...
),
}
info.plist App 1
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
project.pbxproj App 1
PRODUCT_BUNDLE_IDENTIFIER = com.flutter.example;
Output App 2
[VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: PlatformException(, App not found com.flutter.example, null)
#0 AppAvailability.checkAvailability (package:flutter_appavailability/flutter_appavailability.dart:40:9)

How to test flutter url_launcher that email app opens?

I do have a Snackbar with a SnackbarAction which should open the default email app with a default subject and body on tap. I am wondering if there is somehow the possibility to verify if this really happens with some unit tests.
My Snackbar code looks like this:
SnackBar get snackbar =>
SnackBar(
content: Text(message),
action: SnackBarAction(
key: const Key('ErrorSnackbarAction'),
label: AppLocalizations
.of(_context)
.report,
onPressed: () async => await launch('mailto:test#test.com?subject=TestSubject&body=TestBody')));
I am already verifying the appearance which works fine:
group('ErrorSnackbar', () {
testWidgets('appearance test', (WidgetTester tester) async {
await tester.pumpWidget(_generateSnackbarApp());
await _showSnackbar(tester);
expect(find.text(userMessage), findsOneWidget);
expect(find.byWidgetPredicate((Widget widget) =>
widget is SnackBarAction && widget.label == 'Report'), findsOneWidget);
});
testWidgets('error report test', (WidgetTester tester) async {
await tester.pumpWidget(_generateSnackbarApp());
await _showSnackbar(tester);
tester.tap(find.byKey(errorSnackbarAction));
await tester.pump();
// how to verify that the default email app was opened
// with expected subject and body?
});
});
Short answer: You can't.
The launch with mailto is handled by the OS of the device and is out of context of the flutter app.
As the flutter test package focuses on the flutter app, what happens on the OS is out of reach.
You can ensure that launchUrl is called and the expected parameters were passed. The url_launcher package should be tested. Therefore, we can expect that the email app opens, when we call launchUrl with the mailto: schema.
Here is a short introduction on how to test the url_launcher package:
Add plugin_platform_interface and url_launcher_platform_interface to your dev dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
plugin_platform_interface: any
url_launcher_platform_interface: any
Copy the mock_url_launcher_platform.dart from the url_launcher package: https://github.com/flutter/plugins/blob/main/packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart
Now you can test the launchUrl calls like this:
void main() {
late MockUrlLauncher mock;
setUp(() {
mock = MockUrlLauncher();
UrlLauncherPlatform.instance = mock;
});
group('$Link', () {
testWidgets('calls url_launcher for external URLs with blank target',
(WidgetTester tester) async {
FollowLink? followLink;
await tester.pumpWidget(Link(
uri: Uri.parse('http://example.com/foobar'),
target: LinkTarget.blank,
builder: (BuildContext context, FollowLink? followLink2) {
followLink = followLink2;
return Container();
},
));
mock
..setLaunchExpectations(
url: 'http://example.com/foobar',
useSafariVC: false,
useWebView: false,
universalLinksOnly: false,
enableJavaScript: true,
enableDomStorage: true,
headers: <String, String>{},
webOnlyWindowName: null,
)
..setResponse(true);
await followLink!();
expect(mock.canLaunchCalled, isTrue);
expect(mock.launchCalled, isTrue);
});
});
}
Copied the test from https://github.com/flutter/plugins/blob/main/packages/url_launcher/url_launcher/test/link_test.dart
Unit test wise
I think the best way to test it is to wrap url launcher with your own class.
This way you can mock it and check that your logic calls your wrapper class function to launch the mail app. This should be enough.
As for the actual mail app or browser (in my case) you should trust the library todo what it supposed to so no need to unit test it really.

How do I open a web browser (URL) from my Flutter code?

I am building a Flutter app, and I'd like to open a URL into a web browser or browser window (in response to a button tap). How can I do this?
TL;DR
This is now implemented as Plugin
const url = "https://flutter.io";
if (await canLaunchUrl(url))
await launchUrl(url);
else
// can't launch url, there is some error
throw "Could not launch $url";
Full example:
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
void main() {
runApp(new Scaffold(
body: new Center(
child: new RaisedButton(
onPressed: _launchURL,
child: new Text('Show Flutter homepage'),
),
),
));
}
_launchURL() async {
const url = 'https://flutter.io';
final uri = Uri.parse(url);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
} else {
throw 'Could not launch $url';
}
}
In pubspec.yaml
dependencies:
url_launcher: ^6.1.7
Check out the latest url_launcher package.
Special Characters:
If the url value contains spaces or other values that are now allowed in URLs, use
Uri.encodeFull(urlString) or Uri.encodeComponent(urlString) and pass the resulting value instead.
If you target sdk 30 or above canLaunch will return false by default due to package visibility changes: https://developer.android.com/training/basics/intents/package-visibility
in the androidManifest.xml you'll need to add the following directly under <manifest>:
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
Then the following should word - for flutter 3 upwards:
const uri = Uri.parse("https://flutter.io");
if (await canLaunchUrl(uri)){
await launchUrl(uri);
} else {
// can't launch url
}
or for older versions of flutter use this instead:
const url = "https://flutter.io";
if (await canLaunch(url)){
await launch(url);
} else {
// can't launch url
}
launchUrl has a mode parameter which can be used to control where the url gets launched.
So, passing in launchUrl(uri, mode: LaunchMode.platformDefault) leaves the decision of how to launch the URL to the platform
implementation. But you can also specify
LaunchMode.inAppWebView which will use an in-app web view
LaunchMode.externalApplication for it to be handled by an external application
Or LaunchMode.externalNonBrowserApplication to be handled by a non-browser application.
For Flutter:
As described above by Günter Zöchbauer
For Flutter Web:
import 'dart:html' as html;
Then use:
html.window.open(url, name);
Make sure that you run flutter clean if the import doesn't resolve.
For those who wants to implement LAUNCH BROWSER AND EXIT APP by using url_launcher. Remember to use (forceSafariVC: false) to open the url in default browser of the phone. Otherwise, the launched browser exit along with your APP.
await launch(URL, forceSafariVC: false);
The best way is to use url_launcher package .
Add url_launcher as a dependency in your pubspec.yaml file.
dependencies:
url_launcher:
An example of how to use it :
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Flutter is beautiful'),),
body: Center(
child: RaisedButton(
onPressed: _launchURL,
child: Text('Show Flutter homepage'),
),
),
)),
);
}
_launchURL() async {
const url = 'https://flutter.dev';
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
} else {
throw 'Could not launch $url';
}
}
Output :
The launch method takes a string argument containing a URL .
By default, Android opens up a browser when handling URLs. You can
pass forceWebView: true parameter to tell the plugin to open a WebView
instead. If you do this for a URL of a page containing JavaScript,
make sure to pass in enableJavaScript: true, or else the launch method
will not work properly. On iOS, the default behavior is to open all
web URLs within the app. Everything else is redirected to the app
handler.
This is now implemented as Plugin
const url = "https://flutter.io";
final Uri _url = Uri.parse(url);
await launchUrl(_url,mode: LaunchMode.externalApplication);
pubspec.yaml
Add dependencies
dependencies: url_launcher: ^6.0.12
Output
If you want to use url_launcher than please use it in this form
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
url_launcher: ^5.0.2
flutter:
sdk: flutter
This answer is also for absolute beginners: They are thinking behind the flutter sdk.
No that was a failure. The packages were extras and not in the flutter Sdk. These were secondary packages (single small framework helpers).
The PLUGIN plugin works great, as you explain in your examples.
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
final Uri _url = Uri.parse('https://flutter.dev');
void main() => runApp(
const MaterialApp(
home: Material(
child: Center(
child: ElevatedButton(
onPressed: launchUrlStart(url: "https://flutter.dev"),
child: Text('Show Flutter homepage'),
),
),
),
),
);
Future<void> launchUrlStart({required String url}) async {
if (!await launchUrl(Uri.parse(url))) {
throw 'Could not launch $url';
}
}
But when trying to open PDF https://www.orimi.com/pdf-test.pdf it remained blank, the problem was that the browser handled it in its own way. Therefore the solution was to tell it to open with an external application and it worked as expected.
Future<void> launchUrlStart({required String url}) async {
if (!await launchUrl(Uri.parse(url),mode: LaunchMode.externalApplication)) {
throw 'Could not launch $url';
}
}
In pubspec.yaml
#https://pub.dev/packages/url_launcher
url_launcher: ^6.1.5
using the url_launcher package to do the following:
dependencies:
url_launcher: ^latest_version
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
}
Note: Ensure you are trying to open a URI, not a String.