How to run app on mobile web with flutter URI schemes - ios

I'm trying to start my flutter app when it connects to a specific URI on the mobile web.
Among other methods, we decided to use URI schemes.
Assuming my specific URI link is e.g. https://myFlutterTest.URISchemes
Run the URI like this: https://myFlutterTest.URISchemes/home?<parameter>
I determine whether to run the app normally based on /home. After that, the value of <parameter> is stored in the Map.
I succeeded in receiving android as a URI and running it normally.
But IOS didn't succeed.
Added below to info.plist
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>https</string>
</array>
<key>CFBundleURLName</key>
<string>myFlutterTest.URISchemes</string>
</dict>
</array>
main.dart(
The part that receives and determines the URI path)
Widget build(BuildContext context) {
HttpProvider httpProbvider = HttpProvider();
return Platform.isAndroid
? MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: "/",
onGenerateRoute: (RouteSettings path) {
if (path.name!.contains("/home")) {
print("path.name : ${path.name}");
return MaterialPageRoute(
builder: (BuildContext context) => Home(),
);
}
},
)
: CupertinoApp(
debugShowCheckedModeBanner: false,
initialRoute: "/",
onGenerateRoute: (RouteSettings path) {
if (path.name!.contains("/home")) {
print("path.name : ${path.name}");
return CupertinoPageRoute(
builder: (BuildContext context) => Home(),
);
}
},
);
}
}
If you know anything wrong, please let me know. Thank you.

Related

Can't pass headers in flutter_inappwebview & webview_flutter in iOS

versions:
flutter_inappwebview: 5.3.2
webview_flutter: 2.3.1
I'm trying to pass headers to a webview URL, on Android everything is working fine but in ios, I'm getting an unauthorized error displayed.
I tried adding NSAppTransportSecurity in Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
</dict>
also enabled embedded preview
<key>io.flutter.embedded_views_preview</key>
<true/>
but unable to make it work. Below is my code.
code:
webview_flutter:
body: WebView(
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (c) {
c.loadUrl(
url,
headers: {'Authorization': 'Bearer ' + token},
);
},
),
flutter_inappwebview:
body: InAppWebView(
initialUrlRequest: URLRequest(
url: Uri.parse(url),
headers: {'Authorization': 'Bearer ' + token},
),
),
initialize variable in the stateful class and try this solution
bool _init = true;
onLoadStop: (controller, url) async {
if (Platform.isIOS) {
if (_isInit) {
await webViewController!.loadUrl(
urlRequest: URLRequest(
url: url,
headers: headers,
),
);
setState(() {
_isInit = false;
});
}
}
pullToRefreshController!.endRefreshing();
this.url = url.toString();
urlController.text = this.url;
},

Flutter webview is blank on IOS but working on android

I'm using flutter webview to present the payment url in my app using the following class:
class YourWebView extends StatelessWidget {
String url;
bool isFinshed = false;
YourWebView(this.url);
final Completer<WebViewController> _controller =
Completer<WebViewController>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('اكمال عملية الدفع..'),
leading: new IconButton(
icon: new Icon(Icons.close),
onPressed: () {
if(isFinshed) {
Provider.of<MatchProvider>(context, listen: false)
.getMyComingMatches();
Navigator.of(context).popUntil((route) => route.isFirst);
} else {
Navigator.pop(context);
}
}),
),
body: Builder(builder: (BuildContext context) {
return WebView(
initialUrl: Uri.encodeFull(url),
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
debuggingEnabled: true,
onPageFinished: (String url) {
SystemChannels.textInput.invokeMethod('TextInput.hide');
if (url.contains("tap/check?tap_id")) {
isFinshed = true;
}
print('Page finished loading: $url');
},
gestureRecognizers: null,
// gestureNavigationEnabled: false
);
}));
}
The url looks like:
https://xxxxxx.com/tap/check?tap_id=chg_TS05162021120xxxxxxx
Everything is working on Android, but on IOS i get a blank screen and i see this error in xcode debug logs :
WebPageProxy::didFailProvisionalLoadForFrame
I have tried to run another urls on the webview and it was working, but the payment url isn't, even though it's working on Android or other browsers.
I think you maybe tried to load a http link in webview instead of https. In that case you should add the following in your info.plist file.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key><true/>
</dict>
I am not familiar with Flutter, but on iOS, if the system's process for rendering the web content terminated, the webview would be blank, the flutter plugin is handling the situation in here, you may have to check the error and do a reload.

Flutter Bluetooth detection on iOS for scanning beacons

I'm developing an app and I need to scan iBeacons. I used the beacons_plugin (https://pub.dev/packages/beacons_plugin) and it was working well on Android and iOS. But since the 2.0.0 it's not working anymore on iOS. I tried to understand why but I couldn't. Any help please ?
my info.plist :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>MiLo</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>App needs location permissions to scan nearby beacons.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>App needs location permissions to scan nearby beacons.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>App needs location permissions to scan nearby beacons.</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
My widget where I use the plugin :
// All packages needed by the widget
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io' show Platform;
import 'dart:convert';
import 'package:beacons_plugin/beacons_plugin.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_application_1/widgets/beacon_card.dart';
class BeaconDetection extends StatefulWidget {
// Declaration of the variables needed by the widget 1/2
final townId;
final fontsizeValue;
final languageId;
final wordMessageBeforeScan;
final wordButtonLaunchScan;
final wordButtonStopScan;
// Declaration of the variables needed by the widget 2/2
BeaconDetection(
{this.townId,
this.fontsizeValue,
this.languageId,
this.wordMessageBeforeScan,
this.wordButtonLaunchScan,
this.wordButtonStopScan});
#override
_BeaconDetectionState createState() => _BeaconDetectionState();
}
class _BeaconDetectionState extends State<BeaconDetection> {
bool isRunning = false;
String _beaconUuid = '';
double _beaconDistance = 0;
String _beaconDetectedUuid = 'null';
double _beaconDetectedDistance = 0;
final StreamController<String> beaconEventsController =
StreamController<String>.broadcast();
#override
void initState() {
super.initState();
initPlatformState();
}
#override
void dispose() {
beaconEventsController.close();
super.dispose();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
if (Platform.isAndroid) {
// Prominent disclosure
await BeaconsPlugin.setDisclosureDialogMessage(
title: "Need Location Permission",
message: "This app collects location data to work with beacons.");
// Only in case, you want the dialog to be shown again. By Default, dialog will never be shown if permissions are granted.
// await BeaconsPlugin.clearDisclosureDialogShowFlag(false);
}
BeaconsPlugin.listenToBeacons(beaconEventsController);
// Extraction of the name and the uuid of the beacons of the chosen city from the firebase
FirebaseFirestore.instance
.collection('towns/${widget.townId}/beacons')
.snapshots()
.listen((event) {
event.docs.forEach((element) async {
if (element['name'].isNotEmpty == true &&
element['uuid'].isNotEmpty == true &&
element['visibility'] == true) {
await BeaconsPlugin.addRegion(element['name'], element['uuid']);
}
});
});
// When listening the data from beacons detected
beaconEventsController.stream.listen(
(data) {
if (data.isNotEmpty) {
setState(() {
Map _beaconScanned = jsonDecode(data);
_beaconUuid = _beaconScanned['uuid'];
_beaconDistance = double.parse(_beaconScanned['distance']);
});
// print("Beacons DataReceived: " + data);
FirebaseFirestore.instance
.collection('towns/${widget.townId}/beacons')
.where('uuid', isEqualTo: _beaconUuid)
.snapshots()
.listen((event) {
event.docs.forEach((element) {
if (_beaconUuid == element['uuid'] &&
element['visibility'] == true &&
_beaconDistance <= element['distance']) {
print('Beacon: $_beaconUuid | Distance: $_beaconDistance');
_beaconDetectedUuid = _beaconUuid;
_beaconDetectedDistance = _beaconDistance;
}
});
});
}
},
onDone: () {},
onError: (error) {
print("Error: $error");
});
await BeaconsPlugin.runInBackground(
isRunning); // Send 'true' to run in background
if (Platform.isAndroid) {
BeaconsPlugin.channel.setMethodCallHandler((call) async {
if (call.method == 'scannerReady') {
await BeaconsPlugin.startMonitoring();
setState(() {
isRunning = true;
});
}
});
} else if (Platform.isIOS) {
await BeaconsPlugin.startMonitoring();
setState(() {
isRunning = true;
});
}
if (!mounted) return;
}
Widget build(BuildContext context) {
return Column(
children: [
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('towns/${widget.townId}/beacons')
.where('uuid', isEqualTo: _beaconDetectedUuid)
.snapshots(),
builder: (ctx, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) // Initialisation state
return Container(
margin: const EdgeInsets.all(10),
height: 314, // Beacon card's size
child: Center(child: CircularProgressIndicator()),
);
if (_beaconDetectedUuid == 'null' &&
isRunning == false) // Before first scan State
return Container(
margin: const EdgeInsets.all(10),
height: 314, // Beacon card's size
child: Center(child: Text(widget.wordMessageBeforeScan)),
);
if (_beaconDetectedUuid == 'null' &&
isRunning == true) // Launch first scan state
return Container(
margin: const EdgeInsets.all(10),
height: 314, // Beacon card's size
child: Center(
child: CircularProgressIndicator(),
),
);
final beacons = snapshot.data!.docs;
return BeaconCard( // When the detection is done => Display the widget 'BeaconCard' with the following data
title: beacons[0]['title'],
monument: beacons[0]['monument'],
image: beacons[0]['image'],
duration: beacons[0]['duration'],
distance: _beaconDetectedDistance,
townId: widget.townId,
uuid: beacons[0]['uuid'],
fontsizeValue: widget.fontsizeValue,
languageId: widget.languageId,
);
}),
isRunning
? FloatingActionButton.extended( // If 'isRunning' is true => Display a button to stop the scan
icon: Icon(Icons.bluetooth_disabled),
label: Text(widget.wordButtonStopScan),
backgroundColor: Colors.red,
onPressed: () async {
await BeaconsPlugin.stopMonitoring();
setState(() {
isRunning = false;
});
},
)
: FloatingActionButton.extended( // If 'isRunning' is false => Display a button to start the scan
icon: Icon(Icons.bluetooth),
label: Text(widget.wordButtonLaunchScan),
backgroundColor: Theme.of(context).primaryColor,
onPressed: () async {
initPlatformState();
await BeaconsPlugin.startMonitoring();
setState(() {
isRunning = true;
_beaconDetectedUuid = 'null';
});
},
),
SizedBox(
height: 16,
)
],
);
}
}
Thanks a lot

Flutter Webview Apple Pay

In an attempt to use WebViewController.evaluateJavascript('myjs') I get this error in my debug console.
[VERBOSE-2:ui_dart_state.cc(166)] Unhandled Exception: PlatformException(evaluateJavaScript_failed, Failed evaluating JavaScript, JavaScript string was: 'doucment.getElementById("checkout_email").value = "Hell0";'
Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=0, WKJavaScriptExceptionMessage=Unable to run user agent scripts because this document has previously accessed Apple Pay. Documents can be prevented from accessing Apple Pay by adding a WKUserScript to the WKWebView's WKUserContentController., WKJavaScriptExceptionColumnNumber=0, NSLocalizedDescription=A JavaScript exception occurred})
#0 StandardMethodCodec.decodeEnvelope
package:flutter/…/services/message_codecs.dart:572
#1 MethodChannel._invokeMethod
package:flutter/…/services/platform_channel.dart:161
<asynchronous suspension>
#2 MethodChannel.invokeMethod
package:flutter/…/services/platform_channel.dart:334
#3 MethodChannelWebViewPlatform<…>
My Dart file looks like this,
On page load I do a simple check to ensure I execute the js on the correct page. If the check passes the js is executed but unfortunately returns the error above. Ive tried looking all over Google for an answer but ive found nothing similar to the issue I am having.
final Completer<WebViewController> _controller =
Completer<WebViewController>();
TextEditingController _searchQueryController = TextEditingController();
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
elevation: 0,
title: _searchBar(),
leading: _directionButton('backward'),
actions: [_directionButton('forward')],
),
body: WebView(
initialUrl: 'https://google.com',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
onPageStarted: (url) {
_searchQueryController.text = url;
},
onPageFinished: (url) async {
print('Page Finished: ' + url);
final controller = await _controller.future;
if (url.contains('checkouts')) {
await controller.evaluateJavascript('doucment.getElementById("checkout_email").value = "Hello";');
}
},
),
));
}
Try to add this into your info.plist , Replasing domain of your payment gateway
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>yourdomain.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
and Dont forget to re-install app

programmatically change language of flutter i18n apps doesn't work in iOS

I used flutter_i18n plugin (Android Studio) to generate i18n.dart(class S) and S.of(context).locale_msg will return the locale string. The main code is shown below.
Language should be changed programmatically by invoking onLocaleChange(locale) when click the button in HomePage. It works well in Android simulator, but won't change language in iOS simulator. Wonder what's wrong with my code?
class _PaperMoonAppState extends State<PaperMoonApp> {
SpecifiedLocalizationDelegate _localeOverrideDelegate;
void onLocaleChange(Locale locale) {
setState(() {
if (appVars.appConfig.changeLanguage(locale)) {
_localeOverrideDelegate = new SpecifiedLocalizationDelegate(locale);
appVars.saveConfig(); //print save config file...
}
});
}
#override
void initState() {
SpecifiedLocalizationDelegate.onLocaleChange = this.onLocaleChange;
appVars.loadConfig().then((AppConfig _config) {
appVars.appConfig = _config;
setState(() {
_localeOverrideDelegate =
new SpecifiedLocalizationDelegate(appVars.appConfig.getLocale());
});
});
_localeOverrideDelegate =
new SpecifiedLocalizationDelegate(Locale('zh', ''));
super.initState();
}
#override
Widget build(BuildContext context) {
print(_localeOverrideDelegate.overriddenLocale);
return MaterialApp(
debugShowCheckedModeBanner: false,
title: "Paper Moon",
color: Colors.blueAccent,
localizationsDelegates: [
_localeOverrideDelegate,
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
supportedLocales: const <Locale>[
Locale("ja", ""),
Locale("en", ""),
Locale("zh", ""),
],
localeResolutionCallback:
S.delegate.resolution(fallback: _localeOverrideDelegate.overriddenLocale),
home: HomePage(),
// routes: _buildRoutes(),
);
}
}
Custom LocalizationDelegate:
class SpecifiedLocalizationDelegate
extends LocalizationsDelegate<WidgetsLocalizations> {
//class static vars:
//onLocaleChange should be bind to MaterialApp function containing setState().
static LocaleChangeCallback onLocaleChange;
// for instance
final Locale overriddenLocale;
const SpecifiedLocalizationDelegate(this.overriddenLocale);
#override
bool isSupported(Locale locale) => overriddenLocale != null;
#override
Future<WidgetsLocalizations> load(Locale locale) =>
S.delegate.load(overriddenLocale);
#override
bool shouldReload(SpecifiedLocalizationDelegate old) => true;
}
Based on your code, the only thing that seems to be missing is this:
open ios/Runner/Info.plist and add:
<key>CFBundleLocalizations</key>
<array>
<string>ja</string>
<string>en</string>
<string>zh</string>
</array>
As far I as know, by now (march/2019), flutter doesn't yet add automatically the list of supported languages to this file.
I'm using i18n_extensions, but with the same issue...
What worked for me, was use this:
supportedLocales: const <Locale>[
const Locale('en'),
const Locale('pt'),
],
Instead of this:
supportedLocales: const <Locale>[
const Locale('en', 'US'),
const Locale('pt', 'BR'),
],
And then, my i18n.dart. file i've change from this:
extension Localization on String {
static final _t = Translations.from("en_us", {
passwordInput: {
"en_us": "Password",
"pt_br": "Senha",
},
searchingTitle: {
"en_us": "Scanning for devices...",
"pt_br": "Procurando dispositivos...",
},
...
To this:
extension Localization on String {
static final _t = Translations.from("en", {
passwordInput: {
"en": "Password",
"pt": "Senha",
},
searchingTitle: {
"en": "Scanning for devices...",
"pt": "Procurando dispositivos...",
},
It works fine for me.

Resources