i'm still new in using flutter driver in testing, but as far as i know there are few identifiers that we can use to locate / identify elements, like By Text, By Type, etc
But the problem is, the app that i want to test doesn't have the identifier that i can use to locate them (please correct me if i'm wrong).. the widget code of the app looks like this
Widget _buildNextButton() {
return Align(
alignment: Alignment.bottomRight,
child: Container(
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () => _controller.nextPage(),
),
),
);
}
where that widget is on a class that extends StatefulWidget.
How can i locate that icon in my test script and click it? can i use something like this? And what type of finder should i use? (byValueKey? bySemanticLabel? byType? or what?)
static final arrowKey = find.byValueKey(LoginKey.nextButton);
TestDriverUtil.tap(driver, arrowKey);
We have text and value checks here in Flutter Driver but if you don't have that you can always go the the hierarchy of app.
what I mean by hierarchy is so button has fix or specific parent right?
Let's take your example here, We have Align > Container > IconButton > Icon widget hierarchy which will not be true for others like there might be IconButton but not with the Container parent.
or StreamBuilder or anything that we can think of.
Widget _buildNextButton() {
return Align(
alignment: Alignment.bottomRight,
child: Container(
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () => print("clicked button"),
),
),
);
}
This hierarchy should be atleast ideal for top bottom or bottom top approach.
Now what I mean by Top to bottom approach is Align must have IconButton and for bottom to up approach we are saying IconButton must have Align widget as parent.
Here i have taken top down approach so what I'm checking from below code is finding IconButton who is decendent of Align Widget.
also i added firstMatchOnly true as I was checking what happens if same hierarchy appears for both so
test('IconButton find and tap test', () async {
var findIconButton = find.descendant(of: find.byType("Align"), matching: find.byType("IconButton"), firstMatchOnly: true);
await driver.waitFor(findIconButton);
await driver.tap(findIconButton);
await Future.delayed(Duration(seconds: 3));
});
to check for multiple IconButtons with same Align as parent, we need to have some difference like parent should be having Text view or other widget.
find.descendant(of: find.ancestor(
of: find.byValue("somevalue"),
matching: find.byType("CustomWidgetClass")), matching: find.byType("IconButton"), firstMatchOnly: true)
usually I go something like above where I have split the code in seperate file and check for that widget.
But ultimately find something unique about that widget and you can work on that.
**In Lib directory dart class for connecting that widget**
class Testing extends StatelessWidget {
Testing();
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: YourClass(), // Next button containing class that need to test
);
}
}
**In Test directory**
testWidgets('Next widget field test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(Testing());
// find Widget
var buttonFind = find.byIcon(Icons.arrow_forward);
expect(buttonFind, findsOneWidget);
IconButton iconButton = tester.firstWidget(buttonFind);
expect(iconButton.color, Colors.blue);
});
Related
How can I move an Positioned Widget in Flutter that was created at runtime?
Szenario:
Lets say I have a Stack Widget. The children are a variable of type List<Widget>[].
I receive the command to create a new Positioned Widget.
A second stream sends updated x and y positions, those I want to apply on left and top of the dynamically created Positioned Widget.
To do this I added two variables to my class and set them to left and top of the Positioned Widget.
While this works as along as the Positioned Widget is created during build time, it does not work when I dynamically add the Widget to the List<Widget> at runtime. (the Positioned Widget appears, but i can not change left/top through the variables of _MyClass)
Some pseudo code that shows the bits.
class _MyClass extends State<StatefulWidget> {
double x = 0.0;
double y = 0.0;
List<Widget> myList = [];
[...]
void _updateLocation(dx, dy) { // called by a stream...
setState(() {
x = dx;
y = dy;
});
}
[...]
void addHeart() {
myList.clear();
myList.add (
Positioned(
top: y,
left: x,
child:Icon(
Icons.favorite,
color: Colors.pink,
size: 24.0,
),
)
);
}
[...]
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Demo"),
),
body: Center(
child: Container (
child: Stack(
children: myList, //this does not update the top/left of the object
/* //THIS WORKS
children: <Widget>[
Positioned(
top: y,
left: x,
child:Icon(
Icons.favorite,
color: Colors.pink,
size: 24.0,
),
)
]
*/
)
)
),
);
}
I'm new to Flutter, and I propably don't get the mechanics right here, though, I tried to get this working with setState and Keys, but I feel like I'm missing a important bit of information here.
A possible hack I found is to provide the List<Widget> through a function to the Stack Widget and rebuilding the List<Widget> everytime I get an update on x and y. Which essentially deletes and adds the Widget from the stack everytime I get a new x and y... This seems like the worst way to achieve what I want.
How can I do this in a better way, what I'm missing here, why can I update top/left through x and y when creating the widget in build(..) but not when adding it to the list at runtime?
Ofcourse it would not work when you have a predefined list as children of stack. For your widget to move and change its location, flutter needs to actually rebuild the changed widgets, and note that it does call build method each time it re-renders.
So it is not a bad solution to call a function which creates the list each time (as it is exactly what flutter does for all widgets).
You do not need to worry about adding and deleting the items of stack each time the function is called, that is because flutter does not actually re-render anything on the page if it has not changed compared to current content of the page.
I'm working with Cupertino widgets, and need to locally override my global CupertinoTheme, and use CupertinoTheme widget for this purpose. My use case is to force some 'dark' theme when displaying text on top of images, but the issue is general.
In the following sample, I try to change the font size for one text style (from 42px to 21px), but it is not applied: the two texts have the same size (second should be 21px high).
It seems that CupertinoTheme.of(context) does not read the overriden style, contrary to the documentation
Descendant widgets can retrieve the current CupertinoThemeData by calling CupertinoTheme.of
Here is a sample (that can be tested on DartPad):
import 'package:flutter/cupertino.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoApp(
debugShowCheckedModeBanner: true,
theme: CupertinoThemeData(
brightness: Brightness.dark,
textTheme: CupertinoTextThemeData(
navLargeTitleTextStyle: TextStyle(fontSize: 42)
)
),
home: Home()
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: Column(
children: [
Text(
'Hello, World #1!',
style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle
),
CupertinoTheme(
data: CupertinoThemeData(
textTheme: CupertinoTextThemeData(
navLargeTitleTextStyle: TextStyle(fontSize: 21)
)
),
child: Text(
'Hello, World #2!',
style:
CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle
),
),
]
)
);
}
}
You’re getting the theme from the wrong context. The context must be a descendant of the CupertinoTheme widget (or rather the element that will be created from it). Try:
CupertinoTheme(
data: ...,
child: Builder(
builder: (context) => ... CupertinoTheme.of(contex)...
)
)
With the content parameter of the build method you can access anything done by ancestors of the build-method’s widget. Whatever you do in the build method has no effect on it.
Widgets are recipes for creating a tree of Elements. The context parameter that you get in build(er) method is (a reduced interface of) the element created for that widget. The Foo.of(context) methods typically search through the ancestor elements of context to find a Foo. (In some cases there is caching, so it isn’t a slow search.) When you create a tree of widgets in a build method, you’re just creating widgets; the elements will be created after that build method competes. Using a Builder widget, like I did above, delays creation of the widgets in Builder’s builder parameter until after an elements have been created for the Builder (and the widgets above it). So that is a way to get around your problem. Another way would be to create a new StatelessWidget with the widgets that are children of CupertinoTheme in your code, because it will similarly delay the creation of those widgets until after the element for that stateless widget (and its parents) is created.
My problem is simple. I was building a ListTile from the list of documents I get from firebase by iterating through the results. The ListTile contains a leading icon, a title, and a trailing favorite IconButton. The list tile shows perfectly as I want it to. But the problem arises when I try to change the color of the IconButton while a user taps on it. For some reason, the code I wrote isn't doing the trick. What i tried to do was to set the value of the IconButton's color by a ternary which uses a class variable named isFavorited. What i wanted it to do is change the color of the IconButton when i tap on that same IconButton. Here is my code block:
// Builds a tile for each brought up names of taxistops
if (retrievedData.isNotEmpty) {
retrievedData.forEach((element) {
if (element.contains(query) ||
element.contains(query.toUpperCase()) ||
element.contains(query.toLowerCase())) {
ListTile listTile = ListTile(
leading: Icon(Icons.local_taxi),
title: Text(element),
trailing: IconButton(
icon: isFavorited
? Icon(
Icons.star,
color: Colors.amber[400],
)
: Icon(Icons.star_border),
onPressed: () => {
setState(() {
isFavorited = true;
}),
addToFavorite()
},
),
onTap: () => close(context, element),
);
searchedTiles.add(listTile);
}
});
}
Any help is appreciated! Thank you in advance!
I think the problem is because you are adding the widget in the list you should preview it directly inside the widget father (ListView) so it can do the setState correctly and not inside a list you created as a data structure to store elements.
I think that's the issue if it still doesn't work I would need to see how you are showing the list.
I am new to flutter, and am trying to code mobile game. So far I have been able to manage through previous questions on forums, youtube tutorials and example apps. However I have hit a wall that I cannot solve.
Many of the features in my UI are supposed to change based on user behavior but do not update, I am using this DropdownButton as my example but it is a problem I am having elsewhere in the code as well. When the user makes a selection from the DropdownButton it should update the value of the button, but instead only the hint is displayed. I have been able to determine through print statements that the selection is registered.
Based on what I have learned so far I suspect that the issue entails my widget's statefulness or lack thereof. I found a few similar questions which suggested using a StatefulBuilder, however when I tried to implement it the code had errors or else duplicated my entire ListTile group. I also saw suggestions about creating a stateful widget instead but the instructions were too vague for me to follow and implement without errors. I am including snippets of the code below, I am hesitant to paste the whole thing because the app is nearly 2000 lines at this point. Please let me know if any further information or code is needed.
this is the problematic code, see below for the code in context.
DropdownButton(
value: skillChoice,
items: listDrop,
hint: Text("Choose Skill"),
onChanged: (value) {
setState(() {
skillChoice = value;
});
},
),
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hunters Guild',
theme: ThemeData(
primaryColor: Colors.grey,
),
home: Main(),
);
}
}
final List<Combatant> _combatants = List<Combatant>();
//combatant is a class that holds stats like HP which may change during a battle
//they also have a Fighter and a Monster and one of those will be null and the other useful
//depending if faction = "good" or "evil
//I am aware that this may not be the most elegant implementation but that is a problem for another day.
class MainState extends State<Main> {
final List<Fighter> _squad = List<Fighter>();
//Fighter is a class which stores info like HP, name, skills, etc for game character,
//this List is populated in the page previous to _fight
//where the user picks which characters they are using in a given battle
void _fight(Quest theQuest){
for(Fighter guy in _squad){
_combatants.add(Combatant("good", guy, null));
}
_combatants.add(Combatant("evil", null, theQuest.boss));
//quests are a class which store a boss Monster, later on I will add other things like rewards and prerequisites
final tiles = _combatants.map((Combatant comba){ //this structure is from the build your first app codelab
List<DropdownMenuItem<String>> listDrop = [];
String skillChoice = null;
if (comba.faction == "good"){
for (Skill skill in comba.guy.skills){
listDrop.add((DropdownMenuItem(child: Text(skill.name), value: skill.name)));
}
}
else if (comba.faction == "evil"){
for (Skill skill in comba.boss.skills){
listDrop.add((DropdownMenuItem(child: Text(skill.name), value: skill.name)));
}
}
//^this code populates each ListTile (one for each combatant) with a drop down button of all their combat skills
return ListTile(
leading: comba.port,
title: Text(comba.info),
onTap: () {
if(comba.faction == "good"){
_fighterFightInfo(comba.guy, theQuest.boss); //separate page with more detailed info, not finished
}
else{
_monsterFightInfo(theQuest.boss); //same but for monsters
}
},
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
DropdownButton( //HERE IS WHERE THE ERROR LIES
value: skillChoice,
items: listDrop,
hint: Text("Choose Skill"),
onChanged: (value) {
setState(() {
skillChoice = value;
});
},
),
IconButton(icon: Icon(Icons.check), onPressed: (){
//eventually this button will be used to execute the user's skill choice
})
],
),
subtitle: Text(comba.hp.toString()),
);
},
);
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
//I have tried moving most of the above code into here,
//and also implementing StatelessBuilders here and other places in the below code to no effect
final divided = ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
return Scaffold(
appBar: AppBar(
title: Text("Slay "+theQuest.boss.name+" "+theQuest.boss.level.toString()+" "+theQuest.boss.type.name, style: TextStyle(fontSize: 14),),
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: () {
_squadSelect(theQuest);
}),
),
body: ListView(children: divided),
);
},
),
);
}
//there are a lot more methods here which I haven't included
#override
Widget build(BuildContext context) {
//I can show this if you need it but there's a lot of UI building on the main page
//Also might not be the most efficiently implemented
//what you need to know is that there are buttons which call _fight and go to that page
}
class Main extends StatefulWidget {
#override
MainState createState() => MainState();
}
Thank you for any help or advice you can offer.
Its a bit difficult to understand what is going on here, so if possible add the problem areas in a new widget and post it here.
In any case, for now it seems your skillShare variable is local, so every time you are setting state it does not update. Try something like this:
class MainState extends State<Main> {
String skillShare = ""; //i.e make skillShare a global variable
final List<Fighter> _squad = List<Fighter>();
The following question had information which helped me find the answer. Different question but the solution applies. Thanks user:4576996 Sven. Basically I need to reformat from doing my constructing in the void _fight to a new stateful page. This may be useful for anyone else who is using the beginning tutorial as a framework to build their app.
flutter delete item from listview
I want to explain something in my app and add a widget which looks like a notification or chat. I want this widget to be visible for some time and then get dismissed. I tried using tooltip but it is visible only when I click it.
Which widget can I use?
The Dart package intro_views_flutter is what you need, but one of its main limitations is that it is displayed on full screen, if that is not an issue to you, then you should take a look at it. Or you can use a showDialog method inside a Future function this way :
Future showNotification() async {
showDialog<String>(
context: context,
child: new AlertDialog(
title: Text('Note!') ,
contentPadding: const EdgeInsets.all(16.0),
content: //any widget you want to display here
),
);
await new Future.delayed(const Duration(seconds: 5), () {
Navigator.of(context).pop(); // this will dismiss the dialog automatically after five seconds
}
}
then when you need it call:
showNotificaion();