Flutter: Get a PopupMenuButton's PopupMenuItem text in unit tests - dart

I have a PaginatedDataTable that has a DataCell with a PopupMenuButton. WidgetTester can find each DataCell no problem but I cant seem to reference the PopupMenuButton's items to try to select one.
How can i get a PopupMenuButton's PopupMenuItem text in unit tests? Am I using await tester.pump(); correctly to allow the menu to appear?
Here is how im doing it now:
...
expect(find.byIcon(Icons.more_horiz).first, findsOneWidget); // works!
await tester.tap(find.byIcon(Icons.more_horiz).first);
await tester.pump();
var byType = find.text('Quote');
expect(byType, findsOneWidget); // fails!
Which fails with
zero widgets with text "Quote" (ignoring offstage widgets)...
And here is the DataCell markup
...
new DataCell(...),
new DataCell(new PopupMenuButton<quickActions>(
icon: new Icon(Icons.more_horiz),
onSelected: (quickActions action) {
_selectContextAction(action);
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<quickActions>>[
new PopupMenuItem<quickActions>(
value: quickActions.edit,
child: new Text('Edit'),
),
new PopupMenuItem<quickActions>(
value: quickActions.remove,
child: new Text('Remove'),
),
new PopupMenuItem<quickActions>(
value: quickActions.reschedule,
child: new Text('Re-schedule'),
),
new PopupMenuItem<quickActions>(
value: quickActions.bid,
child: new Text('Quote'),
),
],
))

I know this is an old question but I found myself in this same problem and I have solved it.
For those having this same problem, I am posting my answer. My test is a little different than yours.
What is Pump
Triggers a frame after duration amount of time.
This makes the framework act as if the application had janked (missed
frames) for duration amount of time, and then received a v-sync signal
to paint the application.
What is PumpAndSettle
Repeatedly calls pump with the given duration
until there are no longer any frames scheduled. This will call pump at
least once, even if no frames are scheduled when the function is
called, to flush any pending microtasks which may themselves schedule
a frame.
//My Code
//await tester.tap(find.byIcon(Icons.text_fields));
//await tester.pumpAndSettle();
//await tester.tap(find.text(expectedFontSize + ' px'));
//expect(fontSize, expectedFontSize);
//call again if you want to do testing again or something again
//await tester.pumpAndSettle();
var mainButton = find.byIcon(Icons.more_horiz);
expect(mainButton, findsOneWidget);
await tester.tap(mainButton);
await tester.pumpAndSettle();
var childButton = find.text('Quote');
expect(childButton , findsOneWidget); //
For me the code worked only after calling PumpAndSettle Method

Related

How to ignore that element doesn't exist and skip taking a screenshot?

I would like to take a screenshot of an element that might or might not exist
const elem = await page.locator('.selector')
await elem.scrollIntoViewIfNeeded();
await elem.screenshot({path: 'screenshot.png'});
If the element isn't on the page this results in
elementHandle.screenshot: Timeout 30000ms exceeded.
Is there a way to just ignore the nonexistence and move on?
This behaves the same
const elem = await page.locator('.selector')
if(elem) {
await elem.scrollIntoViewIfNeeded();
await elem.screenshot({path: 'screenshot.png'});
}
Try wrapping your logic in try {} catch() {} as the possible throw created by screenshot not existing is going to be passed to catch where it can be ignored, instead crashing runtime.

In Dart, why waiting for a trivial ("empty") StreamController stream immediately exits program?

This is the backbone of a simple program that uses a StreamController to listen on some input stream and outputs some other data on its own stream as a reaction:
import 'dart:async';
main() async {
var c = StreamController(
onListen: (){},
onPause: (){},
onResume: (){},
onCancel: (){});
print("start");
await for (var data in c.stream) {
print("loop");
}
print("after loop");
}
Output:
$ dart cont.dart
start
$ dart
Why does this code exit immediately at the await for line without executing neither print("loop") nor print("after loop") ?
Note: in the original program, onListen() will receive the input stream and subscribe to it. The loop actually works until calling subscription.cancel() on the input stream, when it also suddenly exits, without any chance to clean up.
I was surprised by this, but then again all of async/await code is converted to .then()s and it seems everything can't be perfectly translated.
It seems this is part of the answer: You need to close the stream in another task to get out of the await for. Here is a previous related question. Since we don't close it, lines after await for do not execute. Here's an example that avoids this:
import 'dart:async';
main() async {
var c = StreamController(
onListen: () {},
onPause: () {},
onResume: () {},
onCancel: () {});
print("start");
await Future.wait(<Future>[producer(c), consumer(c)]);
print("after loop");
}
Future producer(StreamController c) async {
// this makes it print "loop" as well
// c.add("val!");
await c.close();
}
Future consumer(StreamController c) async {
await for (var data in c.stream) {
print("loop");
}
}
which prints:
start
after loop
So, moral of the story is,
Never leave a StreamController unattended or weird things will happen.
Some async functions that you wait on will break your code, even bypassing your try/finally block!
I thought I knew async/await inside out, but this second point scares me now.

How to send a message directly from my Flutter app to WhatsApp using UrlLauncher?

I am building an app that has to be able to take an order and send it to a specific WhatsApp number. What exactly am I supposed to do? I can open WhatsApp but I can't figure out how to send a message when opening it.
title: new Text("WhatsApp"),
trailing: new Icon(Icons.message),
onTap: () async {
int phone = 962770593839;
var whatsappUrl = "whatsapp://send?phone=$phone";
await UrlLauncher.canLaunch(whatsappUrl) != null
? UrlLauncher.launch(whatsappUrl)
: print(
"open WhatsApp app link or do a snackbar with
notification that there is no WhatsApp installed");
},
I expect that when I input a TextField and press send that saved String will be able to be sent to the WhatsApp number after launching WhatsApp.
Use the plugin.
url_launcher
Using the following link : https://api.whatsapp.com/send?phone=XXXXXXXXXXX (In place of the Xs type the phone number of the person you want to contact, including the country code, but without the + sign.)
RaisedButton(
onPressed: () async => await launch(
"https://wa.me/${number}?text=Hello"),,
child: Text('Open Whatsapp'),
),
Alternatively
You can use this other plugin.
whatsapp_unilink
The whatsapp_unilink package helps you build HTTP links and provides you with an idiomatic Dart interface that:
import 'package:whatsapp_unilink/whatsapp_unilink.dart';
import 'package:url_launcher/url_launcher.dart';
launchWhatsApp() async {
final link = WhatsAppUnilink(
phoneNumber: '+001-(555)1234567',
text: "Hey! I'm inquiring about the apartment listing",
);
await launch('$link');
}
Try flutter_open_whatsapp plugin.You Directly Send Message to Number
FlutterOpenWhatsapp.sendSingleMessage("918179015345", "Hello");
Link
Open in WhatsApp
You can do it like this.
onPressed: () async {
for (var msg in msgList) {
if (msg["phone"] != null) {
var url = "${baseURL}91${msg['phone']}&text=${msg['messages']}";
print(url);
AndroidIntent intent = AndroidIntent(
action: 'action_view',
data: Uri.encodeFull(url),
package: "com.whatsapp.w4b");
intent.launch();
}
}
},
child: Icon(Icons.send),
),
Use whatsapp_share package
This launches whatsApp with respective number and prefills the text field.
for text and link
Future<void> share() async {
await WhatsappShare.share(
text: 'Whatsapp share text',
linkUrl: 'https://flutter.dev/',
phone: '911234567890',
);
}
Share images, files
_image.path is the file path which you wanna share to whatsapp
Future<void> shareFile() async {
await WhatsappShare.shareFile(
text: 'Whatsapp share text',
phone: '911234567890',
filePath: "${_image.path}",
);
}
Complete example here
I use url_launcher: ^6.1.7 like this.
void launchWhatsapp(
String phone,
String message,
) async {
final url = 'https://wa.me/967$phone?text=$message';
await launchUrlString(
url,
mode: LaunchMode.externalApplication,
);
}
what I want to refer here is the launch model default set to
LaunchMode.platformDefault
and that opens a web page and not WhatsApp when I try to launch WhatsApp
so set the launch model to
model: LaunchMode.externalApplication
has fixed the issue

Check if asset exist in flutter

How I can check if a specific asset exists in Flutter. I'm trying to load some images and sound files and I need to handle the case when these assets do not exist.
I need to check the existence because I have audio files and images for numbers from 1 to 1000. When I build my widgets I use a loop from 1 to 1000 to build it. and there are possibilities that the required file ( the image or the sound for the current number ) does not exist in the assets.
you can try my solution, if you use a simple Image.asset Widget:
Image.asset(
'assets/image.jpg',
errorBuilder: (BuildContext context, Object exception, StackTrace stackTrace) {
return Image.network('path');})
Following Raouf suggestion I handled the case where the assets not exist.
Image loader widget:
Future<Image> _buildImage() async {
String path = "assets/images/contents/${content.id}.jpg";
return rootBundle.load(path).then((value) {
return Image.memory(value.buffer.asUint8List());
}).catchError((_) {
return Image.asset(
"assets/images/null.png",
height: 250.0,
);
});
}
Using the Image widget inside my build method:
FutureBuilder(
future: _buildImage(),
builder: (BuildContext context, AsyncSnapshot<Image> snapshot) {
if (snapshot.connectionState == ConnectionState.done)
return snapshot.data;
else
return Image.asset("assets/images/null.png");
},
),
),
I assume that you are using the AssetBundle class to load your data using the load method which takes ByteData, and when you use this method, it will throws an exception if the asset is not found.
For someone that Flutter IO Dev answer did not work because the exception still appears, this worked for me:
Future<Widget> getDevIcon(String path) async {
try {
await rootBundle.load(path);
return Image.asset(path);
} catch (_) {
return SizedBox.shrink();
}
}

how to add or remove item to the the existing array in firestore ?

Firestore has recently add the new new method FieldValue.arrayUnion(value) and FieldValue.arrayRemove(value) but i couldn't find the implementation in flutter cloud_firestore package. Is there any way to achieve this result ?
Firestore addUpdate: https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array
Here is the simple example to show how to update the array values. My document have an up voters array to add the user id's who have up-votes the document.
configure firestore, the config details can be copied form your google-services.json file
final FirebaseApp app = await FirebaseApp.configure(
name: 'test',
options: const FirebaseOptions(
projectID: 'projid',
googleAppID: 'appid',
apiKey: 'api',
databaseURL: 'your url',
),
);
final Firestore firestore = Firestore(app: app);
await firestore.settings(timestampsInSnapshotsEnabled: true);
update code using transaction
fireStore.runTransaction((Transaction tx) async {
DocumentSnapshot snapshot =
await tx.get(widget._document.reference);
var doc = snapshot.data;
if (doc['upvoters'].contains('12345')) {
await tx.update(snapshot.reference, <String, dynamic>{
'upvoters': FieldValue.arrayRemove(['12345'])
});
} else {
await tx.update(snapshot.reference, <String, dynamic>{
'upvoters': FieldValue.arrayUnion(['12345'])
});
}
});
Hope it helps
Update:
And now it seems that it is supported! With the version 0.8.0.
https://pub.dartlang.org/packages/cloud_firestore
And then use FieldValue.arrayUnion and FieldVale.arrayRemove.
There doesn't seems to be a way currently. Please follow the following two github issues:
https://github.com/flutter/flutter/issues/21148
https://github.com/flutter/flutter/issues/20688

Resources