how do I test BottomNavigationBarItems via FlutterDriver?
FlutterDriver allows accessing Widgets via text, byValueKey, byTooltip and byType.
But none of these methods work out for my App because of following reasons:
text: The App is localized and I need to test the App in multiple languages.
byValueKey: BottomNavigationBarItems do not have key properties.
byTooltip: BottomNavigationBarItems do not have toolTip properties.
byType: byType only returns the first match of the type and no list (needed because I have multiple tabs).
Thank you very much!
Cheers.
Not sure if you have found answer for this question, but I am going to post a solution here which works for me. Basically, BottomNavigationBar has a key property which you need to use. Once Flutter Driver identifies this key, then you can tell driver to tap on any of its child items ie BottomNavigationBarItem.
My screen has 2 bottomNavigationBarItems as shown below and I defined key for their parent widget ie BottomNavigationBar as:
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
key: Key('bottom'),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit, color: Colors.green,),
title: Text('First', style: TextStyle(color: Colors.black),)
),
BottomNavigationBarItem(
icon: Icon(Icons.cast, color: Colors.yellow,),
title: Text('Second', style: TextStyle(color: Colors.black),)
)
],
),
And I wrote a flutter driver test to tap on both items which worked perfectly.
test('bottomnavigationbar test', () async {
await driver.waitFor(find.byValueKey('bottom'));
await driver.tap(find.text('First'));
print('clicked on first');
await driver.tap(find.text('Second'));
print('clicked on second too');
});
Result:
As mentioned by #bsr and #user12563357- you can use the key on Text widget:
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
key: Key('bottom'),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit),
title: Text('First', key: Key('first'),)
),
BottomNavigationBarItem(
icon: Icon(Icons.cast),
title: Text('Second', key: Key('second'),)
)
],
),
and find the text to click on the bar item in test:
final firstItem = find.byValueKey('first');
await driver.tap(firstItem);
btw: you can also find BottomNavigationBarItem using find.ancestor
find.ancestor(of: firstItem, matching: find.byType("BottomNavigationBarItem"));
but you can't tap on it.
You have two options - byIcon or with text by getting the localization from the context.
You can get the state of any kind of StatefulWidget.
final MyWidgetState state = tester.state(find.byType(MyWidget));
with this you can get the context and with that the current localizaion.
final l10n = AppLocalizations.of(state.context);
await tester.tap(find.text(l10n.youTitle));
One other option is to get the widget by Icon:
await tester.tap(find.byIcon(Icons.your_icon));
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
key: Key(`bottom`),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit, color: Colors.green,),
title: InkWell(child:Text(`First`, style: TextStyle(color: Colors.black),key:Key(`First`)),)
),
BottomNavigationBarItem(
icon: Icon(Icons.cast, color: Colors.yellow,),
title: InkWell(child:Text(`Second`, style: TextStyle(color: Colors.black),key:Key(`Second`)),)
)
],
),
test(`bottomnavigationbar test`, () async {
await driver.waitFor(find.byValueKey(`bottom`));
await driver.tap(find.byValueKey(`First`));
print('clicked on first');
await driver.tap(find.byValueKey(`Second`));
print('clicked on second too');
});
Related
Is it possible to make the secondary property of the SwitchListTile tapable? In this case, an icon is used:
SwitchListTile(
title: const Text('Lights'),
value: _lights,
onChanged: (bool value) { setState(() { _lights = value; }); },
secondary: const Icon(Icons.lightbulb_outline), //can this be selected?
)
Ideally, instead of creating another widget, I would like to use the Icon in the secondary property to display a message when the user selects it.
Currently when the icon, or entire widget is selected, the switch is toggled. What is the best way to handle this action?
Thanks.
Wrap your Icon inside InkWell to handle the tap :
secondary: InkWell(
onTap: () {
print("click light");
},
child: const Icon(Icons.lightbulb_outline),
),
More info here: https://docs.flutter.io/flutter/material/InkWell-class.html
You could wrap your Icon in an IconButton.
SwitchListTile(
title: const Text('Lights'),
value: _lights,
onChanged: (value) => setState(() => _lights = value),
secondary: IconButton(
icon: Icon(Icons.lightbulb_outline),
onPressed: () {},
),
)
I am trying to show a menu when a navigation bar item is clicked. This was my attempt:
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: MyAppBar(
title: "Home",
context: context,
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(
icon: new Icon(Icons.book), title: Text('Second')),
BottomNavigationBarItem(
icon: new PopupMenuButton(
icon: Icon(Icons.add),
itemBuilder: (_) => <PopupMenuItem<String>>[
new PopupMenuItem<String>(
child: const Text('test1'), value: 'test1'),
new PopupMenuItem<String>(
child: const Text('test2'), value: 'test2'),
],
),
title: Text('more')),
],
currentIndex: 0,
),
body: new Container()));
}
I encountered two problems. First one is the display of the NavigationBarItem. There is a padding between the icon the title that I could not remove (even by adding padding: EdgeInsets.all(0.0)) (as the picture below shows). And the second is that I need to click exactly on the icon for the menu to appear.
I tried calling showMenu directly (the method that PopupMenuButton calls) when a BottomNavigationBarItem of index=2 (for example) is clicked. But it was tricky how to determine the location of origin where the menu should appear from.
Here's an attempt that uses the showMenu directly and calling the function buttonMenuPosition to get the position for the menu. It's fairly fragile, but you can change the location of the button to the middle for example with bar.size.center instead of bar.size.bottomRight. With some MATH and by manually manipulating Offset objects (if/when you have more than 3 items), you can change the location to have the menu on one that isn't the center or at the end).
RelativeRect buttonMenuPosition(BuildContext c) {
final RenderBox bar = c.findRenderObject();
final RenderBox overlay = Overlay.of(c).context.findRenderObject();
final RelativeRect position = RelativeRect.fromRect(
Rect.fromPoints(
bar.localToGlobal(bar.size.bottomRight(Offset.zero), ancestor: overlay),
bar.localToGlobal(bar.size.bottomRight(Offset.zero), ancestor: overlay),
),
Offset.zero & overlay.size,
);
return position;
}
#override
Widget build(BuildContext context) {
final key = GlobalKey<State<BottomNavigationBar>>();
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text("Home"),
),
bottomNavigationBar: BottomNavigationBar(
key: key,
items: [
const BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text('Home')),
const BottomNavigationBarItem(
icon: Icon(Icons.book), title: Text('Second')),
const BottomNavigationBarItem(
icon: Icon(Icons.add), title: Text('more')),
],
currentIndex: 0,
onTap: (index) async {
final position = buttonMenuPosition(key.currentContext);
if (index == 2) {
final result = await showMenu(
context: context,
position: position,
items: <PopupMenuItem<String>>[
const PopupMenuItem<String>(
child: Text('test1'), value: 'test1'),
const PopupMenuItem<String>(
child: Text('test2'), value: 'test2'),
],
);
}
},
),
body: Container()));
}
Here's my attempt at it:
#override
Widget build(BuildContext context) {
return Material(
child: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text("Home"),
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(
icon: new Icon(Icons.book), title: Text('Second')),
BottomNavigationBarItem(
icon: new Icon(Icons.add),
title: Text('More')),
],
currentIndex: 0,
onTap: (int index) async {
if(index == 2){
await showMenu<String>(
context: context,
position: RelativeRect.fromLTRB(1000.0, 1000.0, 0.0, 0.0),
items: <PopupMenuItem<String>>[
new PopupMenuItem<String>(
child: const Text('test1'), value: 'test1'),
new PopupMenuItem<String>(
child: const Text('test2'), value: 'test2'),
],
elevation: 8.0,
);
}
},
),
body: new Container())));
}
Basically using the showMenu method as you said except I've put the values for the RelativeRect as 1000.0 so that it'll be in the bottom right of any device. You could mess around with these values to get a position more right above the icon but I think having it like this works well:
I've inserted custom icons into my application and when I run the app, the icons and text are white, instead of the original color.
Two Problems:
1)The Icons are originally black but when I insert it to my Bottom Nav Items they become white.
2)Also only the first item has a tittle beneath the icon the rest doesn't.
This is my code
bottomNavigationBar: BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(const IconData(0xe903, fontFamily: 'navBar')),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(const IconData(0xe902, fontFamily: 'navBar')),
title: Text('Ideas')
),
BottomNavigationBarItem(
icon: Icon(const IconData(0xe903, fontFamily: 'navBar')),
title: Text('Profile')
),
BottomNavigationBarItem(
icon: Icon(const IconData(0xe901, fontFamily: 'navBar')),
title: Text('Bag')
),
],
),
//pubspec.yaml file
fonts:
- family: navBar
fonts:
- asset: assets/fonts/ic_navbar.ttf
The 4 icons
You need to add a type for your ButtomNavigationBar
bottomNavigationBar: BottomNavigationBar(
//Add this line will fix the issue.
type: BottomNavigationBarType.fixed,
currentIndex: 0, // this will be set when a new tab is tapped
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: new Icon(const IconData(0xe903, fontFamily: 'navBar')),
title: new Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(const IconData(0xe902, fontFamily: 'navBar')),
title: new Text('Messages'),
),
BottomNavigationBarItem(
icon: Icon(const IconData(0xe903, fontFamily: 'navBar')),
title: Text('Profile'),
),
BottomNavigationBarItem(
icon: Icon(const IconData(0xe901, fontFamily: 'navBar')),
title: Text('Bag')
),
],
),
You can use the following code to change the icon color in bottom navigation bar
BottomNavigationBarItem(
icon:IconTheme(child: Icon(Icons.date_range),
data:IconThemeData(color:Colors.yellow)),
title:Text('Schedule')
)
Though it is a rather old thread I wanted to share a finding regarding this topic because I was in the same situation. According to flutter documentation it is expected behavior that item colors default is white if there are more than 3 items in the bottomnavigationbar and there is no selectedItemColor.
BottomNavigationBarType.shifting, the default when there are four or more items. If selectedItemColor is null, all items are rendered in white. The navigation bar's background color is the same as the BottomNavigationBarItem.backgroundColor of the selected item. In this case it's assumed that each item will have a different background color and that background color will contrast well with white.
Flutter Api reference
try the icons that come in the material icons, https://docs.flutter.io/flutter/material/Icons-class.html to make a kind of debug, if the error continues the error is in another side, can you send all the code and send the assets you use?enter image description here
i'm new to using flutter and i have been stuck for over a week with the above error. i searched this forum for possible solution but not found. this is the code below, kindly assist with correction/advice. thanks in advance:
=================================================================
class AfterSplash extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold( <=1 required argument(s) expected, but 0 found. on the open bracket sign
appBar: new AppBar(
title: new Text('Test'),
),
backgroundColor: Colors.black38,
body:
new ImageCarousel(
<ImageProvider>[
new NetworkImage(
'http://wallpaper-gallery.net/images/images/images-2.jpg'),
new NetworkImage(
'http://wallpaper-gallery.net/images/images/images-10.jpg'),
new NetworkImage(
'http://wallpaper-gallery.net/images/images/images-4.jpg'),
],
interval: new Duration(seconds: 5),
),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: 0,
items: [
new BottomNavigationBarItem(
icon: const Icon(Icons.local_movies),
title: new Text('VIDEOS'),
),
new BottomNavigationBarItem(
icon: const Icon(Icons.camera_alt),
title: new Text('PICTURES'),
),
new BottomNavigationBarItem(
icon: const Icon(Icons.contacts),
title: new Text('BOOKING'),
),
]
)
);
}
}
Are you using Dart2?
Assuming that this is the package you are using for the ImageCarousel, this package does not work with Dart2.
I replicated your code in Dart2 without the ImageCarousel widget and it worked fine.
I am trying to change the selected color of a BottomNavigation icon but all I seem to be achieving is changing the text colours. Please assist:
Currently the text color changes to yellow when selected but the icon stays white, I want it to be yellow too and I have tried setting the icon color of the inactive icons to grey like the caption but it's not budging.
Here is my code:
new Theme(
data: Theme.of(context).copyWith(
canvasColor: Colors.black,
splashColor: Colors.yellowAccent,
unselectedWidgetColor: Colors.green,
primaryColor: Colors.red,
textTheme: Theme.of(context).textTheme.copyWith(caption: new TextStyle(color: Colors.grey))
),
child: new BottomNavigationBar(
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: const Icon(Icons.add_shopping_cart, color: Colors.white,),
title: new Text("Services"),
),
new BottomNavigationBarItem(
icon: new Theme(
data: new ThemeData(
),
child: const Icon(Icons.calendar_today, color: Colors.white,)),
title: new Text("Appointment")
),
new BottomNavigationBarItem(
icon: const Icon(Icons.face, color: Colors.white,),
title: new Text("Profile")
)
],
currentIndex: index,
onTap: (int i){setState((){index = i;});},
fixedColor: Colors.yellowAccent,
type: BottomNavigationBarType.fixed,
),
)
Don't declare the color of icon inside BottomNavigationBarItem.
You should declare it inside BottomNavigationBar as unselectedItemColor and selectedItemColor.
bottomNavigationBar:
BottomNavigationBar(
unselectedItemColor: Colors.green,
selectedItemColor: Colors.yellow,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.add_shopping_cart),
),
],
);
By doing so, your code will work.
You've explicitly set color: Colors.white for each of the icons, so they will be white until you set them otherwise.
You have a couple of options:
1) Set your BottomNavigationBar's type to type: BottomNavigationBarType.fixed and set fixedColor: Colors.orange or whatever color you want. Note that you'll have to remove your color: Colors.white or they will still be white.
2) You could test for the right index being set and then decide which color to set the icon to directly, i.e. color = index == 0 ? selectedColor : unselectedColor for the first item, index==1 for the second, and item==2 for the third.
3) A slight alternative would be to set an IconTheme with color=unselectedColor around the entire BottomNavigationBar, then only set the selected item with color = index == 0 ? selectedColor : null.
I'd recommend reading the BottomNavigationBar documentation, particularly the part about fixed vs shifting, as it describes the answer to the exact problem you're having.
This how you could set the color of the icon:
bottomNavigationBar: BottomNavigationBar(
selectedIconTheme: IconThemeData(color: Colors.yellow),
unselectedIconTheme: IconThemeData(color: Colors.white),