Related
In my app I have a NavigationRail on the left and the main body on the right. I'm using nested Navigators so that when a widget inside the IndexedStack pushes a new route the NavigationRail stays on the screen. But this generates a problem when there is a new Navigator Route on the screen and I try to change the page with NavigationRail it changes the selected icon, but the Navigator route stays on the screen.
Row(
children: [
// GestureDetector uses onHorizontalDragStart to make the Navigation
// Rail extend or shrink
GestureDetector(
onHorizontalDragStart: (details) {
setState(() {
_navRailExtended = !_navRailExtended;
});
},
child: NavigationRail(
// The width of the navigation rail
minExtendedWidth: 180,
// Text and icon themes for selected destinations
selectedIconTheme:
IconThemeData(color: Colors.purple[600]),
selectedLabelTextStyle: TextStyle(
color: Colors.purple[600],
fontWeight: FontWeight.bold,
fontSize: 16),
// Text and icon themes for unselected destinations
unselectedIconTheme:
IconThemeData(color: Colors.grey[200]),
unselectedLabelTextStyle: TextStyle(
color: Colors.grey[400],
fontWeight: FontWeight.bold,
fontSize: 15),
// Background color of the navigation rail, typical
// color for the dark theme in Flutter
backgroundColor: const Color.fromARGB(255, 47, 47, 47),
// extended makes the navigation rail wider
extended: _navRailExtended,
selectedIndex: _pageIndex,
onDestinationSelected: (int index) {
setState(() {
_pageIndex = index;
});
},
destinations: const <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.explore_rounded, size: 35),
label: Text('Discover'),
),
NavigationRailDestination(
icon: Icon(Icons.home_filled, size: 35),
label: Text('Notebook'),
),
NavigationRailDestination(
icon: Icon(Icons.games_sharp, size: 35),
label: Text('Games'),
),
NavigationRailDestination(
icon: Icon(Icons.analytics_outlined, size: 35),
label: Text('Analytics'),
),
],
),
),
// A vertical divider between the navigation rail and the body
const VerticalDivider(thickness: 1.5, width: 1),
// Expanded makes the body of the windows app centered
// (without it the row widget would screw up the sizing of the app)
Expanded(
// Navigator makes all new routes inside the main pages
// opened with 'Navigator.pop()' ignore the navigation rail,
// i.e. the navigation rail will always stay opened
child: Navigator(
onGenerateRoute: (settings) {
return MaterialPageRoute(builder: ((context) {
// Indexed Stack keeps the state of each page unchanged
return IndexedStack(
index: _pageIndex,
children: const [Discover(), NoteBook(), Games(), Analytics()],
);
}));
},
),
)
],
)
App on the start:
When there is a new Navigator route on the screen:
I try to change the page:
What it should do:
My requirement is something like this:
Flutter - change appbar icon when receiving notification
But AppBar 'cart icon' which is to be notified on 'addToCart' button click implemented in another dart file. Am I doing something wrong by placing the AppBar and the rest in two different dart files? Let me know how to proceed.
You can use a global ValueNotifier variable. Then you can change it's content regardless the page in which you are.
Assuming this variable declaration ValueNotifier cartCounterNotifier = ValueNotifier(0);, you can update your AppBar action like this
appBar: AppBar(
// title and other
actions: [
ValueListenableBuilder(
valueListenable: cartCounterNotifier,
builder: (context, cartCounter, child) {
return Stack(
children: [
IconButton(
icon: Icon(Icons.shopping_cart_rounded),
onPressed: () {},
),
Positioned(
child: 0 < cartCounter ? Container(
decoration: ShapeDecoration(
color: Colors.red,
shape: CircleBorder(),
),
padding: EdgeInsets.all(2.0),
child: Text("$cartCounter", style: TextStyle(
color: Colors.white,
),),
) : SizedBox(),
),
],
);
}
)
],
),
Update:
So to update your action button, simply change the content of your variable like this
cartCounterNotifier.value = cartCounterNotifier.value + 1; // or another value
Widget build(BuildContext context) {
print("homepage");
final navigation = Provider.of<NavigationProvider>(context, listen: false);
return Consumer<ThemeProvider>(builder: (context, themeData, _) {
return Scaffold(
backgroundColor: themeData.getTheme['mainBackground'],
appBar: AppBar(
backgroundColor: themeData.getTheme['appBar'],
elevation: 0.0,
title: Text(
"INTRADAE",
style: GoogleFonts.montserrat(
color: themeData.getTheme['textColor'],
),
),
actions: <Widget>[
Container(
margin: EdgeInsets.only(right: 5),
child: IconButton(
icon: Icon(
Icons.account_balance_wallet,
size: 30,
color: themeData.getTheme['textColor'],
),
onPressed: null,
),
)
],
),
body: Consumer<NavigationProvider>(
builder: (context, navigationProvider, _) =>
navigationProvider.getNavigation),
bottomNavigationBar: CurvedNavigationBar(
index: 0,
height: 50,
color: themeData.getTheme['secondaryBackground'],
backgroundColor: themeData.getTheme['mainBackground'],
buttonBackgroundColor: themeData.getTheme['mainBackground'],
items: <Widget>[
Icon(Icons.home, color: Colors.white),
Icon(Icons.shopping_cart, color: Colors.white),
Icon(Icons.person, color: Colors.white)
],
onTap: (index) {
navigation.updateNavigation(_pages[index]);
},
),
);
});
I am trying to update whatever is inside the body of the scaffold when updateNavigation takes place. Everything seemed fine, until I put a print inside the build method of the main widget and noticed that it was being called on every update the to NavigationProvider.
I am scratching my head since. I can't understand why this widget has to rebuild when only the Consumers should rebuild which in this case is inside the body of the Scaffold.
Coming from a React background this just seems weird to me.
Can anyone tell me why this is happening?
The problem was solved once I performed a hot restart. I then read about Hot Reload in Flutter the docs and realised that the changes I was making to the code weren't going to be seen unless I performed a hot restart.
Although the code posted here has
listen:false
in,
Provider.of<NavigationProvider>(context, listen: false)
But, I originally did not have that in my code. And when adding that argument too didn't seem to help, I decided to post a question here.
The problem was hot reload all along. Should've have made sure before posting.
I have a Product listing where their is a heart icon button to mark it as favorite. using provider I am able to update the data but for the below case its not working directly.
Consumer<ProductProvider>(
builder: (ctx, product, child) => IconButton(
icon: Icon(
widget.product.isFavourite ? Icons.favorite : Icons.favorite_border,
color:
widget.product.isFavourite ? Colors.orange : iconColor,
),
onPressed: () => setState((){
widget.product.isFavourite = !widget.product.isFavourite;
}) ,
),
),
the above code is in my product card widget which is been used in Future Builder > List View builder.
if I click on the heard Icon in that list view and then navigate to that product details page. the Data is updated or that product is show to be favorite which is correct.
Now the Issue, From my Product Details Page if I click on the Heart Icon to mark that product as favorite or not favorite ( if already favorite ), then the data is updated on that page, but not in the List View Builder from previous page where the Product Card have the Consumer been used, unless the page is rebuild or I scroll the list view where more or other products are loaded which make the data update and then that change is reflected.
My product model is as below,
class ProductModel with ChangeNotifier {
...
// change product favorite status.
void toggleIsFavourite() {
this.isFavourite = !this.isFavourite;
notifyListeners();
}
}
// product details page heart button code
actions: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: IconButton(
icon : Icon(
product.isFavourite ? Icons.favorite : Icons.favorite_border,
color:
product.isFavourite ? red : iconColor,
),
onPressed: () => setState((){
product.toggleIsFavourite();
}),
),
),
],
Test Case
1) I Click on heart icon in product card and not inside heart icon on product details page.
result : Everything is working.
2) I Click on heart icon in product detail page and not inside heart icon on product card widget which is on home page.
result : heart on product card on home page is not updated unless the data of the list view is refreshed or the home page is rebuild even though I am using Consumer to just update the value for that heart icon in product card.
if you set the correct notifier above these screen, try
Consumer<ProductProvider>(
builder: (ctx, product, child) => IconButton(
icon: Icon(
product.isFavourite ? Icons.favorite : Icons.favorite_border,
color:
product.isFavourite ? Colors.orange : iconColor,
),
onPressed: () =>
product.toggleIsFavourite(),
),
),
this way your icon gets updated based on your product provider notifications.
Make sure you can access the isFavourite property of your ProductModel.
same way with product details
actions: <Widget>[
Consumer<ProductProvider>(
builder: (ctx, product, child) => Padding(
padding: const EdgeInsets.only(right: 10.0),
child: IconButton(
icon : Icon(
product.isFavourite ? Icons.favorite : Icons.favorite_border,
color: product.isFavourite ? red : iconColor,
),
onPressed: () => product.toggleIsFavourite(),
),
),
),
],
and you avoid changing widgets' states altogether.
this may not be 100% correct, but it has worked for me
I want to have a row of IconButtons, all next to each other, but there seems to be pretty big padding between the actual icon, and the IconButton limits. I've already set the padding on the button to 0.
This is my component, pretty straightforward:
class ActionButtons extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.lightBlue,
margin: const EdgeInsets.all(0.0),
padding: const EdgeInsets.all(0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
IconButton(
icon: new Icon(ScanrIcons.reg),
alignment: Alignment.center,
padding: new EdgeInsets.all(0.0),
onPressed: () {},
),
IconButton(
icon: new Icon(Icons.volume_up),
alignment: Alignment.center,
padding: new EdgeInsets.all(0.0),
onPressed: () {},
)
],
),
);
}
}
I want to get rid of most of the light blue space, have my icons start earlier on the left, and closer to each other, but I can't find the way to resize the IconButton itself.
I'm almost sure this space is taken by the button itself, 'cause if I change their alignments to centerRight and centerLeft they look like this:
Making the actual icons smaller doesn't help either, the button is still big:
thanks for the help
Simply pass an empty BoxConstrains to the constraints property and a padding of zero.
IconButton(
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
)
You have to pass the empty constrains because, by default, the IconButton widget assumes a minimum size of 48px.
Two ways to workaround this issue.
Still Use IconButton
Wrap the IconButton inside a Container which has a width.
For example:
Container(
padding: const EdgeInsets.all(0.0),
width: 30.0, // you can adjust the width as you need
child: IconButton(
),
),
Use GestureDetector instead of IconButton
You can also use GestureDetector instead of IconButton, recommended by Shyju Madathil.
GestureDetector( onTap: () {}, child: Icon(Icons.volume_up) )
It's not so much that there's a padding there. IconButton is a Material Design widget which follows the spec that tappable objects need to be at least 48px on each side. You can click into the IconButton implementation from any IDEs.
You can also semi-trivially take the icon_button.dart source-code and make your own IconButton that doesn't follow the Material Design specs since the whole file is just composing other widgets and is just 200 lines that are mostly comments.
Wrapping the IconButton in a container simply wont work, instead use ClipRRect and add a material Widget with an Inkwell, just make sure to give the ClipRRect widget enough border Radius 😉.
ClipRRect(
borderRadius: BorderRadius.circular(50),
child : Material(
child : InkWell(
child : Padding(
padding : const EdgeInsets.all(5),
child : Icon(
Icons.favorite_border,
),
),
onTap : () {},
),
),
)
Instead of removing a padding around an IconButton you could simply use an Icon and wrap it with a GestureDetector or InkWell as
GestureDetector(
ontap:(){}
child:Icon(...)
);
Incase you want the ripple/Ink splash effect as the IconButton provides on click wrap it with an InkWell
InkWell(
splashColor: Colors.red,
child:Icon(...)
ontap:(){}
)
though the Ink thrown on the Icon in second approach wont be so accurate as for the IconButton, you may need to do some custom implementation for that.
Here's a solution to get rid of any extra padding, using InkWell in place of IconButton:
Widget backButtonContainer = InkWell(
child: Container(
child: const Icon(
Icons.arrow_upward,
color: Colors.white,
size: 35.0,
),
),
onTap: () {
Navigator.of(_context).pop();
});
I was facing a similar issue trying to render an Icon at the location the user touches the screen. Unfortunately, the Icon class wraps your chosen icon in a SizedBox.
Reading a little of the Icon class source it turns out that each Icon can be treated as text:
Widget iconWidget = RichText(
overflow: TextOverflow.visible,
textDirection: textDirection,
text: TextSpan(
text: String.fromCharCode(icon.codePoint),
style: TextStyle(
inherit: false,
color: iconColor,
fontSize: iconSize,
fontFamily: icon.fontFamily,
package: icon.fontPackage,
),
),
);
So, for instance, if I want to render Icons.details to indicate where my user just pointed, without any margin, I can do something like this:
Widget _pointer = Text(
String.fromCharCode(Icons.details.codePoint),
style: TextStyle(
fontFamily: Icons.details.fontFamily,
package: Icons.details.fontPackage,
fontSize: 24.0,
color: Colors.black
),
);
Dart/Flutter source code is remarkably approachable, I highly recommend digging in a little!
A better solution is to use Transform.scale like this:
Transform.scale(
scale: 0.5, // set your value here
child: IconButton(icon: Icon(Icons.smartphone), onPressed: () {}),
)
You can use ListTile it gives you a default space between text and Icons that would fit your needs
ListTile(
leading: Icon(Icons.add), //Here Is The Icon You Want To Use
title: Text('GFG title',textScaleFactor: 1.5,), //Here Is The Text Also
trailing: Icon(Icons.done),
),
I like the following way:
InkWell(
borderRadius: BorderRadius.circular(50),
onTap: () {},
child: Container(
padding: const EdgeInsets.all(8),
child: const Icon(Icons.favorite, color: Colors.red),
),
),
enter image description here
To show splash effect (ripple), use InkResponse:
InkResponse(
Icon(Icons.volume_up),
onTap: ...,
)
If needed, change icons size or add padding:
InkResponse(
child: Padding(
padding: ...,
child: Icon(Icons.volume_up, size: ...),
),
onTap: ...,
)