In flutter, we use platform channels that allows us to call platform-specific APIs whether available in Kotlin or Java code on Android, or in Swift or Objective-C code on iOS.
How to achieve the same with web in flutter ? How can one use a npm package and write some javascript code and send the result to flutter? Is this even possible ? There is official docs for writing platform specific code for Android and iOS, but I couldn't find any docs for writing platform specific code for web.
Also, I tried using the js package. If this is the one that has to be used for this case, how to use it ?
This is what I do to display Hubspot chat on Flutter Web.
I have a folder for Hubspot with:
index.html
script.js
style.css
Then a Flutter Widget with webview_flutter_plus plugin:
class HubspotWebview extends StatefulWidget {
#override
_HubspotWebviewState createState() => _HubspotWebviewState();
}
class _HubspotWebviewState extends State<HubspotWebview> {
final _javascriptChannels = Set<JavascriptChannel>();
bool loading = true;
#override
void initState() {
super.initState();
_javascriptChannels.add(JavascriptChannel(
onMessageReceived: (JavascriptMessage message) {
debugPrint('message: ' + message.message);
_toggleLoading();
},
name: 'Loading'));
}
#override
Widget build(BuildContext context) {
final path = '${kIsWeb ? 'assets/' : ''}assets/hubspot_web_page/index.html';
final key = 'web_bot_key';
if (kIsWeb) {
ui.platformViewRegistry.registerViewFactory(
key,
(int viewId) => IFrameElement()
..width = '640'
..height = '360'
..src = path
..style.border = 'none'
..onLoadedData.listen((event) {
_toggleLoading();
}));
}
return Scaffold(
appBar: new AppBar(
backgroundColor: MyColors.blue_business,
title: MyText.subtitle(
getText('business_help_chat', backUpText: 'Help Chat'),
color: MyColors.white_rice,
)),
body: Stack(
children: [
kIsWeb
? HtmlElementView(viewType: key)
: WebViewPlus(
javascriptMode: JavascriptMode.unrestricted,
initialUrl: path,
javascriptChannels: _javascriptChannels,
),
if (loading)
Center(child: CircularProgressIndicator()),
],
),
);
}
void _toggleLoading() => setState(() => loading = !loading);
}
On Javascript file Loading.postMessage('') triggers toggleLoading() on Flutter:
function onConversationsAPIReady() {
window.hsConversationsSettings = {
inlineEmbedSelector: '#hubspot-conversations-inline-parent',
enableWidgetCookieBanner: true,
disableAttachment: true
};
window.history.pushState({}, 'bot', '?bot=true');
window.HubSpotConversations.widget.refresh({openToNewThread: true});
Loading.postMessage('');
}
if (window.HubSpotConversations) {
onConversationsAPIReady();
} else {
window.hsConversationsOnReady = [onConversationsAPIReady];
}
Related
I have a barcode scanner working fine on Android, but I am struggling to find plugins that support a web app.
This is the closest one I've found that seems to be getting somewhere:
https://pub.dev/packages/ai_barcode
But I can't really get anything to happen.
Here is the code I'm using currently:
import 'package:flutter/material.dart';
import 'package:ai_barcode/ai_barcode.dart';
class WebBarcodeScannerPage extends StatefulWidget {
// void resultCallback (String result) {
// debugtext
// }
#override
_WebBarcodeScannerPageState createState() => _WebBarcodeScannerPageState();
}
class _WebBarcodeScannerPageState extends State<WebBarcodeScannerPage> {
ScannerController _scannerController;
String _debugText = 'debug';
#override
void initState () {
super.initState();
_scannerController = ScannerController(scannerResult: (r) => resultCallback(r));
// _scannerController = ScannerController(scannerResult: (result) {
// resultCallback(result);
// }, scannerViewCreated: () {
// final TargetPlatform platform = Theme.of(context).platform;
// if (TargetPlatform.iOS == platform) {
// Future.delayed(const Duration(seconds: 2), () {
// _scannerController
// ..startCamera()
// ..startCameraPreview();
// });
// } else {
// _scannerController
// ..startCamera()
// ..startCameraPreview();
// }
// });
}
resultCallback (String r) {
print(r);
setState(() {
_debugText = r;
});
}
_body () {
return Column(
children: [
Text(_debugText),
TextButton(
child: Text('Start camera'),
onPressed: () {
_scannerController.startCamera();
},
),
TextButton(
child: Text('Start preview'),
onPressed: () {
_scannerController.startCameraPreview();
},
),
TextButton(
child: Text('Stop camera'),
onPressed: () {
_scannerController.stopCamera();
},
),
TextButton(
child: Text('Stop preview'),
onPressed: () {
_scannerController.stopCameraPreview();
},
),
PlatformAiBarcodeScannerWidget(platformScannerController: _scannerController),
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: _body(),
);
}
}
The camera preview shows up in a window, but I can't scan any barcodes or QR codes. The debug text never changes.
Again, my goal is to be able to read barcodes into a string on a mobile web app.
You can use our product Cognex Mobile Barcode SDK
Download page for all supported platforms - https://cmbdn.cognex.com/download#Platforms
Knowledge Base for more information about the integration - https://cmbdn.cognex.com/v2.6.x/knowledge/flutter/license-keys
https://pub.dev/packages/cmbsdk_flutter
Regards,
You should add jsQR.js to web folder.
You can find that HERE
you can use the source example from ai_barcode.
QRCodeDartScanView(
typeCamera: TypeCamer.front,
scanInvertedQRCode: true,
resolutionPreset: QRCodeDartScanResolutionPreset.ultraHigh,
formats: const [
BarcodeFormat.QR_CODE,
],
onCapture: (Result result) {
printInfo(info: result.text);
},
);
Just put this piece of code inside a build method. The camera opens and read qr codes.
simple_barcode_scanner (which leverages html5-qrcode for web apps) worked best for my PWA, in fact it was the only package of several I tried that met my requirements of being able to read both EAN-13 and GS1 data matrix from a web app -- and it worked immediately the first time, and it does not require editing index.html. (I couldn't use ai_barcode due to a hard-to-resolve dependency conflict between ai_barcode and barcode, and barcode is a must-have for my web app -- but even without the dependency issue I struggled to make ai_barcode work reliably).
If you decide to use simple_barcode_scanner note that it does not work on desktop browsers, only mobile browsers, so you'll need a check like this:
import 'package:flutter/foundation.dart';
import 'package:simple_barcode_scanner/simple_barcode_scanner.dart';
...
final isWebMobile = kIsWeb &&
(defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.android);
if (isWebMobile) {
String scanResult = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SimpleBarcodeScannerPage()),
);
print('scan result = $scanResult');
} else {
print('Scanning is only supported for mobile web browsers.');
}
I am using "mobile_number(version - 1.0.3)" plugin to get mobile number in flutter app, am running in original device but i couldn't get mobile number.instead of errors i can get mobile number as null along with other sim details as shown in screen shot.
help me to resolve this problem, i had just copy pasted the example given by plugin that is the code
plugin link
It says:
Note: If the mobile number is not pre-exist on sim card it will not return te phone number.
I think mobile number does not pre-exist on the SIM if the SIM is not original (i.e. replaced)
If the phone number isn't stored on the sim (aka null), then you can't get it from anywhere else, in that case you probably want to forward the user to a different page where they can type the phone number using TextField and then store it somewhere
Use Mobile_number package to get mobile number and other details. For example
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobile_number/mobile_number.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _mobileNumber = '';
List<SimCard> _simCard = <SimCard>[];
#override
void initState() {
super.initState();
MobileNumber.listenPhonePermission((isPermissionGranted) {
if (isPermissionGranted) {
initMobileNumberState();
} else {}
});
initMobileNumberState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initMobileNumberState() async {
if (!await MobileNumber.hasPhonePermission) {
await MobileNumber.requestPhonePermission;
return;
}
String mobileNumber = '';
// Platform messages may fail, so we use a try/catch PlatformException.
try {
mobileNumber = (await MobileNumber.mobileNumber)!;
_simCard = (await MobileNumber.getSimCards)!;
} on PlatformException catch (e) {
debugPrint("Failed to get mobile number because of '${e.message}'");
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_mobileNumber = mobileNumber;
});
}
Widget fillCards() {
List<Widget> widgets = _simCard
.map((SimCard sim) => Text(
'Sim Card Number: (${sim.countryPhonePrefix}) - ${sim.number}\nCarrier Name: ${sim.carrierName}\nCountry Iso: ${sim.countryIso}\nDisplay Name: ${sim.displayName}\nSim Slot Index: ${sim.slotIndex}\n\n'))
.toList();
return Column(children: widgets);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
children: <Widget>[
Text('Running on: $_mobileNumber\n'),
fillCards()
],
),
),
),
);
}
}
how can I show image picked by file_picker in web while the file path is null in web platform ?
If the path was not null, showing the image is too easy with Image.file(File):
Image.file(context.select<BlogProvider, File>((BlogProvider p) => p.image))
but It can not create File for image in web because browsers don't give file path and It's null.
Future<void> pickImage() async {
/// If [withReadStream] is set, picked files will have its byte data available as a [Stream<List<int>>]
/// which can be useful for uploading and processing large files.
FilePickerResult result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['jpg', 'jpeg'],
withReadStream: true,
);
if (result != null) {
PlatformFile file = result.files.single; //single means I am Picking just One file not more
_blogImage = File(file.path);//Null in web ,but Ok in Android
notifyListeners();
} else {
// User canceled the picker
}
}
When withReadStream is set to true, selected image can be accessed as:
file.readStream.listen((event) {
_blogImage = Image.memory(event);
notifyListeners();
});
but when withReadStream is false:
_blogImage = Image.memory(file.bytes);
notifyListeners();
And although file.path is null in flutter for web, the file.name is set correctly and we can display it.
More info here
Another way (without file_picker package):
import 'dart:html' as html;
// ...
void pickFile() {
final input = html.FileUploadInputElement()..accept = 'image/*';
input.onChange.listen((event) {
if (input.files.isNotEmpty) {
fileName = input.files.first.name; // file name without path!
// synthetic file path can be used with Image.network()
url = html.Url.createObjectUrl(input.files.first);
});
}
});
input.click();
}
You can use Image.memory()
an exemple using the package universal_html
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: DemoApp0(),
),
),
);
}
class DemoApp0 extends StatefulWidget {
DemoApp0({
Key key,
}) : super(key: key);
#override
_DemoApp0State createState() => _DemoApp0State();
}
class _DemoApp0State extends State<DemoApp0> {
final Map<String, Uint8List> files = {};
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
TextButton(
onPressed: ()=>pickWebFile(),
child: Text("select file"),
),
Column(
children: files.entries
.map((e) => Column(
children: [
Text(e.key),
SizedBox(
width: 200,
height: 300,
child: Image.memory(e.value),
)
],
))
.toList(),
)
],
),
);
}
Future<void> pickWebFile() async {
List<html.File> webFiles = [];
html.InputElement uploadInput = html.FileUploadInputElement();
uploadInput.click();
uploadInput.onChange.listen((e) {
webFiles = uploadInput.files;
for (html.File webFile in webFiles) {
var r = new html.FileReader();
Uint8List fileData;
r.readAsArrayBuffer(webFile);
r.onLoadEnd.listen((e) async {
fileData = r.result;
if (webFile.size < 4194304) {
setState(() {
files[webFile.name] = fileData;
});
}
});
}
});
}
}
I have a pdf reader in my app, with this function I get de pdf from URL and save the file in local path
Future<File> getFileFromUrl(String url) async {
try {
var data = await http.get(url);
var bytes = data.bodyBytes;
var dir = await getApplicationSupportDirectory();
File file = File("${dir.path}/some.pdf");
File urlFile = await file.writeAsBytes(bytes);
return urlFile;
} catch (e) {
throw Exception("Error opening url file");
}
}
After this process, I call a class to show this PDF in a new route
import 'package:flutter/material.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart';
class CustomPdfView extends StatefulWidget {
final String title;
final String urlPdf;
CustomPdfView(
this.title,
this.urlPdf
);
#override
_CustomPdfViewState createState() => _CustomPdfViewState();
}
class _CustomPdfViewState extends State<CustomPdfView> {
//int _totalPages = 0;
//int _currentPage = 0;
bool pdfReady = false;
//PDFViewController _pdfViewController;
#override
Widget build(BuildContext context) {
print('Aqui entra antes: ${widget.urlPdf}');
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Stack(
children: <Widget>[
PDFView(
filePath: widget.urlPdf,
autoSpacing: true,
enableSwipe: true,
pageSnap: true,
swipeHorizontal: true,
nightMode: false,
onError: (e) {
print("error $e");
},
onRender: (_pages) {
setState(() {
//_totalPages = _pages;
pdfReady = true;
});
},
onViewCreated: (PDFViewController vc) {
//_pdfViewController = vc;
},
onPageChanged: (int page, int total) {
setState(() {});
},
onPageError: (page, e) {},
),
!pdfReady
? Center(
child: CircularProgressIndicator(),
)
: Offstage()
],
),
);
}
}
All works fine in debug mode, but when I run my app on release, the app crashes in CustomPdfView.
I don't know what is the error, I already added, STORAGE permissions in my /app/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
And I don't know how can I get error line in the console, because the app is running on release.
same issue.....
i fixed that one
clean your project after build your apk
if still problem persist
than try these commands
flutter build apk --no-shrink
flutter build apk --release --split-per-abi --no-shrink
your way of reading pdf is very nice, here you try to launch pdf in your app,
i almost did the same way.
where i was getting Uint8List from url of pdf, and reading it in my app using printing 5.9.3
but when size of pdf or number of pages more then my app keep getting crash, and also before launching pdf in my app i need to save it locally as you did.
so, to solve this i just need to use flutter_cached_pdfview: ^0.4.1
using this flutter_cached_pdfview i don't need to save pdf locally, just pass the url of pdf and you can launch it in your app without worrying about size of pdf or number of pages in pdf.
i just launch using a button and i pass url of pdf
// navigate in another screen
onPressed: () => nextScreen(
PDFViewerPage(
url: widget.magazineModel.fileUrl,
title: widget.magazineModel.name,
),
),
and MyPdfViewerPage is below
class PDFViewerPage extends StatelessWidget {
final String url;
final String title;
const PDFViewerPage({
Key? key,
required this.title,
required this.url,
}) : super(key: key);
#override
Widget build(BuildContext context) {
debugPrint('url : $url');
return Scaffold(
appBar: AppBar(
leading: const LeadingIconComponent(), // for popScreen()
title: Text(title),
),
body: const PDF().cachedFromUrl(
Uri.parse(url).toString(),
placeholder: (progress) => const LoadingComponent(),
errorWidget: (error) {
debugPrint('errorInOprnPDF : $error');
return const ErrorPageComponent(
title: 'File Not Exist',
content: "This file is either removed or not valid",
image: "assets/images/empty_data_component/new data not found.svg",
);
},
),
);
}
}
I have a flutter code snippet which listens for postMessage from my iframe page.
(flutter_webview_plugin: ^0.3.9+1)
flutterWebviewPlugin.onStateChanged.listen((viewState) async {
String script = 'window.addEventListener("message", receiveMessage, false);' +
'function receiveMessage(event) {console.log(\'receiving data from child , data as follows: \',event.data)}';
flutterWebviewPlugin.evalJavascript(script);
}
I would like to trigger specific flutter functions if event.data returns a specific value ,
camera value would trigger my specific function that calls the camera plugin and so on. Meaning to say that my target iFrame will attempt to do a cross-origin communication via postMessage method.
For Cordova, I could do something like this:
window.addEventListener( "message" , function( event )
{
else if( event.data.indexOf( "camera" ) >= 0 )
{
//Trigger Camera Function
How do I go about doing this for flutter?
2 solution for you:
Solution 1: Recommend
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
final Set<JavascriptChannel> jsChannels = [
JavascriptChannel(
name: 'Print',
onMessageReceived: (JavascriptMessage message) {
print('message.message: ${message.message}');
}),
].toSet();
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
"/": (_) => WebviewScaffold(
url: Uri.dataFromString(
'<html><button onclick="Print.postMessage(\'test\');">Click me</button></html>',
mimeType: 'text/html')
.toString(),
appBar: new AppBar(title: new Text("Widget webview")),
javascriptChannels: jsChannels,
),
},
);
}
}
I/flutter (22119): message.message: test
Solution 2: If you want to keep your window.post. (Maybe you would like to use the iframe from another platform)
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
final Set<JavascriptChannel> jsChannels = [
JavascriptChannel(
name: 'Print',
onMessageReceived: (JavascriptMessage message) {
print('message.message: ${message.message}');
}),
].toSet();
void main() {
final flutterWebviewPlugin = FlutterWebviewPlugin();
flutterWebviewPlugin.onStateChanged.listen((state) async {
if (state.type == WebViewState.finishLoad) {
String script =
'window.addEventListener("message", receiveMessage, false);' +
'function receiveMessage(event) {Print.postMessage(event.data);}';
flutterWebviewPlugin.evalJavascript(script);
}
});
return runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
"/": (_) => WebviewScaffold(
url: Uri.dataFromString(
'<html><button onclick="window.postMessage(\'test\', \'*\');">Click me</button></html>',
mimeType: 'text/html')
.toString(),
appBar: new AppBar(title: new Text("Widget webview")),
javascriptChannels: jsChannels,
),
},
);
}
}
I/flutter (22119): message.message: test
pubspec.yaml:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
flutter_webview_plugin: 0.3.9+1
Reference:
https://github.com/fluttercommunity/flutter_webview_plugin/issues/309
https://github.com/fluttercommunity/flutter_webview_plugin/pull/457
https://github.com/fluttercommunity/flutter_webview_plugin/issues/364
https://github.com/fluttercommunity/flutter_webview_plugin/pull/523/files
https://github.com/fluttercommunity/flutter_webview_plugin/issues/305