Flutter Plaid package showing blank screen on iOS - android

Good day,
I have a flutter app which I have integrated a Plaid flutter package, it works well on android but shows a white screen on iOS.
I have added
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
</dict>
in the Info.plist file, but this doesn't seem to make it work.
Below are screenshots
Please I need help on what to do to make iOS platform work.
Here is my configuration
Configuration configuration = Configuration(
plaidPublicKey: '$PLAID_PUBLIC_KEY',
plaidBaseUrl: 'https://cdn.plaid.com/link/v2/stable/link.html',
plaidEnvironment: '$PLAID_ENV',
environmentPlaidPathAccessToken:
'https://sandbox.plaid.com/item/public_token/exchange',
environmentPlaidPathStripeToken:
'https://sandbox.plaid.com/processor/stripe/bank_account_token/create',
// plaidClientId: 'yourPlaidClientId',
// secret: plaidSandbox ? 'yourSecret' : '',
clientName: '$PLAID_CLIENT_NAME',
// webhook: 'Webhook Url',
products: 'auth, transactions',
selectAccount: 'true',
plaidClientId: null);
FlutterPlaidApi flutterPlaidApi = FlutterPlaidApi(configuration);
WidgetsBinding.instance.addPostFrameCallback((_) {
// Add Your Code here.
});
flutterPlaidApi.launch(context, (Result result) async {
// show loader screen when returning back to the app
showLoadingScreen(context, message: 'Processing...');
// send the data to the api
var response = await BankService().linkUserAccountWithSila(
accountName: result.accountName,
publicToken: result.token,
email: 'email#example.com');
final responseJson = json.decode(response.body);
if (response.statusCode >= 200 && response.statusCode <= 299) {
var token = await getToken();
var client = new http.Client();
List<String> urls = [
'getDefaultAccount',
'all',
];
try {
List<http.Response> list =
await Future.wait(urls.map((urlId) => client.get(
'$kBaseUrl/account/$urlId',
headers: {HttpHeaders.authorizationHeader: "Bearer $token"},
)));
if (list[0].statusCode == 200 && list[1].statusCode == 200) {
var defaultAccount = jsonDecode(list[0].body);
var plaidAccounts = jsonDecode(list[1].body);
Provider.of<TransferProvider>(context, listen: false)
.updatePlaidBankAccounts(
plaidAccount:
plaidAccounts['data'] != null ? plaidAccounts['data'] : [],
account: defaultAccount['data'],
);
}
} catch (e) {} finally {
client.close();
}
Navigator.pop(context);
Toast.show('Account linked successfully', context,
duration: Toast.LENGTH_LONG, gravity: Toast.CENTER);
} else {
Toast.show('Something went wrong, please try again later', context,
duration: Toast.LENGTH_LONG, gravity: Toast.CENTER);
// error
}
}, stripeToken: false);
}

Try this code: https://github.com/flutter/flutter/issues/49483
Diclaimer: this is not my code. I am copying it here so that if the original post gets deleted, the source code is still available here. All credits to the original author.
Steps to Reproduce
Register for a free sandbox testing account at Plaid (running the webview in sandbox requires a public_key)
Create new project and add plaid_screen.dart to lib
In plaid_screen.dart assign the public key from Plaid into the queryParameters "key" key
Replace default main.dart content with the setup below
add webview_flutter: ^0.3.19+5 to pubspec.yaml
add <key>io.flutter.embedded_views_preview</key><true/> to ios/Runner/info.plist
Run main.dart
main.dart:
import 'package:bug/plaid_screen.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: PlaidScreen.id,
routes: {
PlaidScreen.id: (context) => PlaidScreen(),
},
);
}
}
plaid_screen.dart:
(Note: public key must be obtained and pasted below)
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
String authority = 'cdn.plaid.com';
String unencodedPath = '/link/v2/stable/link.html';
Map<String, String> queryParameters = {
"key": "{{PASTE_PUBLIC_KEY}}",
"product": "auth",
"apiVersion": "v2", // set this to "v1" if using the legacy Plaid API
"env": "sandbox",
"clientName": "Test App",
"selectAccount": "true",
};
// documentation: https://plaid.com/docs/#webview-integration
class PlaidScreen extends StatefulWidget {
static const id = 'plaid_screen_id';
#override
_PlaidScreenState createState() => _PlaidScreenState();
}
class _PlaidScreenState extends State<PlaidScreen> {
Uri uri = Uri.https(authority, unencodedPath, queryParameters);
Completer<WebViewController> _controller = Completer<WebViewController>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: WebView(
javascriptMode: JavascriptMode.unrestricted,
initialUrl: uri.toString(),
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
navigationDelegate: (NavigationRequest navRequest) {
debugPrint("NavigationRequest URL: ${navRequest.url}");
if (navRequest.url.contains('plaidlink://')) {
return NavigationDecision.prevent;
}
debugPrint(navRequest.url.toString());
return NavigationDecision.navigate;
},
),
),
);
}
}

Related

Problem loading a pdf file and rendering it in screen

Im triying to load a pdf file and render it in screen with the following code:
import 'package:flutter/material.dart';
import 'package:pdf_viewer_plugin/pdf_viewer_plugin.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:file_picker/file_picker.dart';
import 'dart:io';
import 'dart:developer';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
FileLoad createState() => FileLoad();
}
class FileLoad extends State<MyApp> {
String _filePath = "";
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test PDF',
theme: ThemeData(primarySwatch: Colors.blue,),
home:
Scaffold(
backgroundColor: Colors.grey,
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: "Home"),
BottomNavigationBarItem(icon: Icon(Icons.folder), label: "Load File")
],
onTap: (int index) async {
var go = 1;
if (index == 1) {
var status = await Permission.storage.status;
if (status != PermissionStatus.granted) {
var result = await Permission.storage.request();
if (result != PermissionStatus.granted) {
go = 0;
}
}
if (go == 1) {
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null) {
File file = File(result.files.single.path!);
setState(() {
_filePath = file.path;
log('_filePath: $_filePath');
});
}
}
}
},
),
body:
PdfView(path: _filePath, gestureNavigationEnabled: true,)
)
);
}
I can see the log with the correct filepath selected in the explorer, but nothing is rendered in the screen. What is wrong with the code?
Any error is triggered in the console.
For me it looks like that the PdfView itself doesn't load the newest path even with a setState(). I had this problem with other PDF-Viewer plugins.
I solved it by wrapping the PDFView into a if statement and while there is no path loaded some sort of loading Widget is displayed.
Like so:
String? _filePath;
body:
_filePath != null ? PdfView(path: _filePath, gestureNavigationEnabled: true,): Text("loading")

How to Solve this error CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:359))

I am new to flutter and developing an app but I am facing an issue regarding a post request to the asp.net core API method.
This below is the flutter code that I am using to post data to the asp.net core API method.
Future registerUser(
String userFullName, String username, String email, String password) async {
var body = jsonEncode({
"UserFullName": userFullName,
"Username": username,
"Email": email,
"Password": password
});
final response = await http.post(
Uri.parse(GetURI.baseURI() + 'Account/Register'),
body: body,
headers: {"content-type": "application/json"});
print("Hello Body! " + response.statusCode.toString());
print(response.body);
return null;
}
On button click, I am calling the above function to post data but getting an error. The code for the button click is below:
onPressed: () async {
final validationSuccess = _formKey.currentState!.validate();
if (validationSuccess) {
_formKey.currentState!.save();
final formData = _formKey.currentState!.value;
final userData = await registerUser(
_formKey.currentState!.fields['username']!.value,
_formKey.currentState!.fields['email']!.value,
_formKey.currentState!.fields['email']!.value,
_formKey.currentState!.fields['password']!.value);
print(userData!.userFullName);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$formData'),
duration: Duration(seconds: 5),
));
}
}
On clicking the button it gives me the error like below:
[ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: HandshakeException: Handshake error in client (OS Error:
CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:359))
This is the Class code from where I am returning my URL:
class GetURI {
static String baseURI() {
return 'https://10.103.78.29:44318/api/';
}
}
My main.dart file
import 'dart:io';
import 'package:bugsmashmobileapp/Screen/LoginScreen/login_body.dart';
import 'package:bugsmashmobileapp/Screen/SignupScreen/signup_body.dart';
import 'package:bugsmashmobileapp/Screen/WelcomeScreen/getting_started_screen.dart';
import 'package:flutter/material.dart';
class MyHttpOverrides extends HttpOverrides {
#override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
}
}
void main() {
HttpOverrides.global = new MyHttpOverrides();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'BUGSMASH APP',
theme: ThemeData(
primarySwatch: Colors.blue, scaffoldBackgroundColor: Colors.white),
home: GettingStartedScreen(),
routes: {
SignupScreen.routeName: (context) => SignupScreen(),
LoginScreen.routeName: (context) => LoginScreen()
},
);
}
}
I have tried many ways and searched a lot but not able to get a good way to solve this error. If anyone can help it would be appreciated.
When I tried ngrok the problem is solved for me.
First I created an account on ngrok (Click here to go to website) and then you need to install its software to generate a URL and forwarding it to your API Project. Then you can use that URL in your Flutter Project without any issue.

Refreshing or rebuilding flutter widget after it has been fully initialised

I am trying to make a Telegram client for android using the tdlib flutter port. I am currently attempting to make a contact list of sorts, by requesting it from telegram and making a listview of textbuttons.
The only issue is that since the library is async, I get the contact list after the layout has been initialized. Is it possible to somehow rebuild the layout or update it to make the list load properly.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:fima/services/telegram_service.dart';
import 'package:tdlib/td_api.dart' show TdError;
import 'package:provider/provider.dart';
import 'package:tdlib/td_api.dart' as TdApi;
class ContactListScreen extends StatefulWidget {
#override
_ContactListScreenState createState() => _ContactListScreenState();
}
class _ContactListScreenState extends State<ContactListScreen> {
final String title = 'Contact list';
bool _loadingStep = false;
String _Error;
String route = "initRoute";
List<TextButton> contacts = [];
#override
void initState() {
super.initState();
_getContacts(onError: _handelError,);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
backgroundColor: Color(0xD3232323),
),
body: Container(
child:
ListView (
children: contacts,
),
),
);
}
Future _getContacts(
{
void Function(TdError) onError,
}) async {
final result = await context.read<TelegramService>().send(
TdApi.GetContacts(
),
);
if (result is TdError && onError != null) {
onError(result);
}
TdApi.Users users = result;
for (var i = 0; i < users.totalCount; i++) {
final result = await context.read<TelegramService>().send(
TdApi.GetUser(userId: users.userIds[i]),
);
TdApi.User user = result;
print(user.firstName + " " + user.lastName);
final contact = TextButton(
onPressed: () {
print("Test");
},
child: Text(user.firstName + " " + user.lastName),
);
setState(() {
contacts.add(contact);
});
}
}
void _handelError(TdError error) async {
setState(() {
_loadingStep = false;
_Error = error.message;
});
}
}
I have attempted to use setState, but without much success, could anyone be so kind as to provide me with the solution to this problem?
Using the FutureBuilder might help. It is a widget that builds itself based on the latest snapshot of interaction with a Future.
You can modify your build to return a FutureBuilder something like this:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getContacts,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
//Use snapshot data to build by returning your Container with List
}
else{
//Return a CircularProgressIndicator
}
}
}
Refer the documentation on the FutureBuilder class here.

Flutter: Adding App Update Dialog for iOS and Android

I am currently working on Notification Feature so when a new Update is availible the User gets a Dialog where he can choose to Update or not. I'm doing it with Firebase Remote Config where i have a Parameter called "force_update_current_version" where i then add the Value for the Version for checking. But I do get following errors.
Thanks for your help and i wish you a healty start into the new Year.
Main.dart Code
import 'checkUpdate.dart';
#override
void initState() {
try {
versionCheck(**context**);
} catch (e) {
print(e);
}
**super**.initState();
}
context error: Undefined name 'context'.
Try correcting the name to one that is defined, or defining the name.
super error: Invalid context for 'super' invocation.
checkUpdate.dart Code
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:package_info/package_info.dart';
import 'package:flutter/cupertino.dart';
const APP_STORE_URL = 'https://apps.apple.com/us/app/appname/idAPP-ID';
const PLAY_STORE_URL =
'https://play.google.com/store/apps/details?id=APP-ID';
versionCheck(context) async {
//Get Current installed version of app
final PackageInfo info = await PackageInfo.fromPlatform();
double currentVersion = double.parse(info.version.trim().replaceAll(".", ""));
//Get Latest version info from firebase config
final RemoteConfig remoteConfig = await RemoteConfig.instance;
try {
// Using default duration to force fetching from remote server.
await remoteConfig.fetch(expiration: const Duration(seconds: 0));
await remoteConfig.activateFetched();
remoteConfig.getString('force_update_current_version');
double newVersion = double.parse(remoteConfig
.getString('force_update_current_version')
.trim()
.replaceAll(".", ""));
if (newVersion > currentVersion) {
_showVersionDialog(context);
}
} on FetchThrottledException catch (exception) {
// Fetch throttled.
print(exception);
} catch (exception) {
print('Unable to fetch remote config. Cached or default values will be '
'used');
}
}
//Show Dialog to force user to update
_showVersionDialog(context) async {
await showDialog<String>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
String title = "New Update Available";
String message =
"There is a newer version of app available please update it now.";
String btnLabel = "Update Now";
String btnLabelCancel = "Later";
return Platform.isIOS
? new CupertinoAlertDialog(
title: Text(title),
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text(btnLabel),
onPressed: () => _launchURL(**Config**.APP_STORE_URL),
),
FlatButton(
child: Text(btnLabelCancel),
onPressed: () => Navigator.pop(context),
),
],
)
: new AlertDialog(
title: Text(title),
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text(btnLabel),
onPressed: () => _launchURL(**Config**.PLAY_STORE_URL),
),
FlatButton(
child: Text(btnLabelCancel),
onPressed: () => Navigator.pop(context),
),
],
);
},
);
}
_launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
Config Error for App and Play Store: Undefined name 'Config'.
Try correcting the name to one that is defined, or defining the name.
In checkUpdate.dart we need to import the firebase_remote_config package that exposes the RemoteConfig class:
import 'package:firebase_remote_config/firebase_remote_config.dart';
Make sure to install it before.
The versionCheck() function shall be invoked from a StatefulWidget, hence, a good place to call it would be inside the first screen Widget, for example:
class FirstScreen extends StatefulWidget {
const FirstScreen({ Key key }) : super(key: key);
#override
_FirstScreenState createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
#override
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => versionCheck(context));
}
#override
Widget build(BuildContext context) {
return Container(color: const Color(0xFFFFE306));
}
}

Flutter webview intercept and add headers to all requests

Using the webview_flutter package i could load my website and add session cookies to the initial URL.
_controller.future.then((controller) {
_webViewController = controller;
Map<String, String> header = {'Cookie': 'ci_session=${widget.sessionId}'};
_webViewController.loadUrl('https://xxxx.com', headers: header);
});
In order to keep the session going i need to add the same header for all requests not just for the initial one.
Is there any way to intercept all requests and modify them by adding headers to them?
the closest thing i found was navigationDelegate but it only returns a NavigationDecision which isn't useful in my case.
You can use my plugin flutter_inappwebview, which is a Flutter plugin that allows you to add inline WebViews or open an in-app browser window and has a lot of events, methods, and options to control WebViews.
If you need to add custom headers for each request, you can use the shouldOverrideUrlLoading event (you need to enable it using useShouldOverrideUrlLoading: true option).
Instead, if you need to add cookies to your WebView, you can just use the CookieManager class (CookieManager.setCookie method to set a cookie).
Here is an example that set a cookie (named ci_session) in your WebView and also set a custom header (named My-Custom-Header) for each request:
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
InAppWebViewController webView;
CookieManager _cookieManager = CookieManager.instance();
#override
void initState() {
super.initState();
_cookieManager.setCookie(
url: "https://github.com/",
name: "ci_session",
value: "54th5hfdcfg34",
domain: ".github.com",
isSecure: true,
);
}
#override
void dispose() {
super.dispose();
}
#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(
initialUrl: "https://github.com/",
initialHeaders: {'My-Custom-Header': 'custom_value=564hgf34'},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
useShouldOverrideUrlLoading: true
),
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (InAppWebViewController controller, String url) {},
onLoadStop: (InAppWebViewController controller, String url) async {
List<Cookie> cookies = await _cookieManager.getCookies(url: url);
cookies.forEach((cookie) {
print(cookie.name + " " + cookie.value);
});
},
shouldOverrideUrlLoading: (controller, shouldOverrideUrlLoadingRequest) async {
print("URL: ${shouldOverrideUrlLoadingRequest.url}");
if (Platform.isAndroid || shouldOverrideUrlLoadingRequest.iosWKNavigationType == IOSWKNavigationType.LINK_ACTIVATED) {
controller.loadUrl(url: shouldOverrideUrlLoadingRequest.url, headers: {
'My-Custom-Header': 'custom_value=564hgf34'
});
return ShouldOverrideUrlLoadingAction.CANCEL;
}
return ShouldOverrideUrlLoadingAction.ALLOW;
},
))
])),
),
);
}
}
in flutter_webview case
onWebViewCreated: (WebViewController webViewController) {
this.webViewController = webViewController;
},
onPageStarted: (url) {
webViewController.loadUrl(url, headers: headers);
},
use this

Categories

Resources