How to test flutter url_launcher that email app opens? - flutter

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.

Related

FB.login() called before FB.init() - Flutter

Trying to:
Authorize via facebook login on web
Problem:
I get the error: FB.login() called before FB.init().
What I'm doing:
I'm using package flutter_facebook_auth: ^5.0.6 for my flutter project. It works fine with Android and iOS, but fails on web.
The main.dart-file initialises facebook before I call the login, so I'm not sure what I need to do. This is my main-file:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
if(kIsWeb){
await FacebookAuth.i.webAndDesktopInitialize(
appId: "provided in the real code",
cookie: true,
xfbml: true,
version: "v14.0"
);
}
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const ProviderScope(child: App()));
}
.
.
.

flutter web not working due to await statement

I am having issues with Flutter web when I use await statement,
void main() async {
//debugPaintSizeEnabled = true;
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
this will not display anything on the browser and throws and error:
ChromeProxyService: Failed to evaluate expression 'title': InternalError: Expression evaluation in async frames is not supported. No frame with index 39..
I am stuck :(
debugging testing nothing worked
Run flutter channel stable followed by flutter upgrade --force
When using Firebase, you need to initalize it with options for the specific platform that you are using. Here goes and example on how to configure it for Flutter Web:
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
void main() async {
//debugPaintSizeEnabled = true;
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options = FirebaseOptions(
apiKey: 'YOUR API KEY',
appId: 'YOUR APP ID',
messagingSenderId: 'YOUR MESSAGING SENDER ID',
projectId: 'YOUR PROJECT NAME',
authDomain: 'YOUR AUTH DOMAIN (IF YOU HAVE)',
databaseURL: 'YOUR DATABASE URL (IF YOU USE FIREBASEDATABASE)',
storageBucket: 'YOUR STORAGE BUCKET',
)
);
runApp(MyApp());
}
All this information is available on your Firebase Project Console, just search for it. Hope it helps!

hydrated_bloc: The parameter 'storageDirectory' is required

I updated hydrated_bloc from 6.1.0 to the latest 7.0.1 and I got a warning in:
HydratedBloc.storage = await HydratedStorage.build(); The parameter 'storageDirectory' is required.
When I changed to what the new documentation suggested
HydratedBloc.storage = await HydratedStorage.build(
storageDirectory: await getTemporaryDirectory(),); The function 'getTemporaryDirectory' isn't defined.
I also tried:
HydratedBloc.storage = await HydratedStorage.build(storageDirectory: await getApplicationDocumentsDirectory(),); The function 'getApplicationDocumentsDirectory' isn't defined
Both getTemporaryDirectory and getApplicationDocumentsDirectory are part of the path_provider package, so, you have to import it in your main.dart file
Yes you need path Provider Flutter package, you might also experience this error "StorageNotFound (Storage was accessed before it was initialized), or The setter 'storage' isn't defined for the type 'HydratedBloc' in Android Studio
instead define it as below: `
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final storage = await HydratedStorage.build(
storageDirectory: await getApplicationDocumentsDirectory(),
);
HydratedBlocOverrides.runZoned(
() => runApp(MyApp(
appRouter: AppRouter(),
connectivity: Connectivity(),
)),
storage: storage,
);
}
`
You can get the full code here

How can I dial the phone from Flutter?

I am building a Flutter app, and I'd like to dial a phone number in response to a button tap. What's the best way to do this?
Thanks!
This method will open the dialer :
_launchCaller() async {
const url = "tel:1234567";
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
EDIT:
In case anybody facing errors:
Add url_launcher: in the pubspec.yaml & run flutter get
Also import 'package:url_launcher/url_launcher.dart';
Typically, to interact with the underlying platform, you have to write platform specific code and communicate with the same using platform channels. However, Flutter provides some points of integration with the platform out of the box. To dial the phone for instance, you can use the UrlLauncher.launch API with the tel scheme to dial the phone.
Something like UrlLauncher.launch("tel://<phone_number>"); should work fine on all platforms.
Do note that this will not work in the simulators. So make sure you are using an actual device to test this.
You can use the url_launcher widget (https://pub.dev/packages/url_launcher)
Add this to your package's pubspec.yaml file: dependencies: url_launcher: ^5.7.10
Install it: $ flutter pub get
Import it import 'package:url_launcher/url_launcher.dart';
Inside your class, define this method so you can call from any action in your code:
Future<void> _makePhoneCall(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
inside the build widget:
IconButton(icon: new Icon(Icons.phone),
onPressed: ()
{
setState(() {
_makePhoneCall('tel:0597924917');
});
},
),
Note 1: you should write the phone number with prefix 'tel': 'tel:0123456789'
Note 2: sometimes it will not work well until you close the app in your mobile and reopen it, so flutter can inject the code of the new widget successfully.
Its too easy
import 'package:flutter/material.dart';
import 'package:flutter_phone_direct_caller/flutter_phone_direct_caller.dart';
void main() {
runApp(Scaffold(
body: Center(
child: RaisedButton(
onPressed: _callNumber,
child: Text('Call Number'),
),
),
));
}
_callNumber() async{
const number = '08592119XXXX'; //set the number here
bool res = await FlutterPhoneDirectCaller.callNumber(number);
}
URL launcher has updated their dependency so here is the updated way to do the job. it will work for android ios
final Uri launchUri = Uri(
scheme: 'tel',
path: number,
);
await launchUrl(launchUri);
Who has not used this plugin just imoprt this plugin.
UrlLauncher
Use url_launcher.
import 'package:url_launcher/url_launcher.dart';
openDialPad(String phoneNumber) async {
Uri url = Uri(scheme: "tel", path: phoneNumber);
if (await canLaunchUrl(url)) {
await launchUrl(url);
} else {
print("Can't open dial pad.");
}
}
Note: Don't forgot to update info.plist and AndroidManifest.xml as per the documentation.
Follow the below steps:
add the url_launcher: Latest version dependency in your pubspec.yaml
Go to \android\app\src\main\AndroidManifest.xml.
add the queries lines before the <application, like this :
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flutter">
<queries>
<intent>
<action android:name="android.intent.action.DIAL" />
<data android:scheme="tel" />
</intent>
</queries>
<application ...
For more queries, follow this more queries
4.Add the dialer function, and set the url, final url ="tel:$phoneNumber"; like this:
Future<void> dialNumber(
{required String phoneNumber, required BuildContext context}) async {
final url = "tel:$phoneNumber";
if (await canLaunch(url)) {
await launch(url);
} else {
ShowSnackBar.showSnackBar(context, "Unable to call $phoneNumber");
}
return;
}
Done
This is what worked for me as of January 2023
NB: The major difference from the other answers above is my #4
ALSO: You can now use test this in Emulator
Steps:
1.Add url_launcher: latest to pubspec.yaml
2.Configuration:
For IOS: Add tel scheme passed to canLaunchUrl as LSApplicationQueriesSchemes entries in your Info.plist file.
Like this:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>tel</string>
</array>
For Android: Add tel scheme passed to canLaunchUrl as queries entries in your AndroidManifest.xml [ie \android\app\src\main\AndroidManifest.xml] file.
Just like this:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flutApp">
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="tel" />
</intent>
</queries>
<application
3.Import url_launcher to your file: ie import 'package:url_launcher/url_launcher.dart';
4.Code
Future<void> _dialNumber(String phoneNumber) async {
final Uri launchUri = Uri(
scheme: 'tel',
path: phoneNumber,
);
await launchUrl(launchUri);
}
Source: url_launcher

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.