How to show correct Icon in IconButton Flutter - flutter

I have a button in the AppBar and I want to change its icon on click, but nothing works.
If you check the type of the icon inside onPressed, then the condition is triggered depending on which button should be, but it is not displayed.
bool toggle = true;
late Widget searchWidget = IconButton(
onPressed: (){
setState(() {
toggle = !toggle;
});
},
icon: toggle ? const Icon(Icons.search) : const Icon(Icons.cancel),
);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
searchWidget,
],
title: searchBar
),
body: displayBody,
bottomNavigationBar: _bottomMenu,
);
}

change searchWidget to method to get updated UI,
Widget searchWidget() => IconButton(
onPressed: () {
setState(() {
toggle = !toggle;
});
},
icon: toggle ? const Icon(Icons.search) : const Icon(Icons.cancel),
);
And use like
actions: [
searchWidget(),
],

Related

IconButton selectedIcon not toggling

The play button should toggle to a pause button when I press it. It is currently not doing that. I'm changing the state of the task isRecording attribute and it's printing to show that it is changing each time I press the button, but the selectedIcon is not showing. It's just showing the original icon.
class TestScreen extends StatefulWidget {
const TestScreen({super.key});
#override
State<TestScreen> createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
Task task = Task(name: 'Test Task', order: 0, isRecording: false);
#override
Widget build(BuildContext context) {
print(task.isRecording);
return Scaffold(
appBar: AppBar(
title: const Text('Test Screen'),
),
body: Center(
child: IconButton(
icon: const Icon(Icons.play_arrow),
isSelected: task.isRecording,
selectedIcon: const Icon(Icons.pause),
onPressed: () {
setState(() {
task.isRecording = !task.isRecording;
});
},
),
),
);
}
}
The selectedIcon feature is only available for Material3.
This property is only used if [ThemeData.useMaterial3] is true.
Solution:
Wrap the IconButton in a Theme and set inside the data the useMaterial3 to true.
return Scaffold(
appBar: AppBar(
title: const Text("Test Screen"),
),
body: Center(
child: Theme(
data: ThemeData(useMaterial3: true),
child: IconButton(
icon: Icon(Icons.play_arrow),
isSelected: !task.isRecording,
selectedIcon: Icon(Icons.pause),
onPressed: () {
setState(() {
task.isRecording = !task.isRecording;
});
},
),
),
),
);
Your IconButton should look like this
IconButton(
icon: task.isRecording ? Icon(Icons.play_arrow) : Icon(Icons.pause),
isSelected: task.isRecording,
selectedIcon: const Icon(Icons.pause),
onPressed: () {
setState(() {
task.isRecording = !task.isRecording;
});
},
),
The catch is that you have to change the icon every time you are setting the state.
Hope this helps!

Toggle icon in IconButton

I am trying to toggle the IconButton icon when pressed on it. This is what I have so far:
class _AppsScreenState extends State<AppsScreen> {
late Future<AllApps> activateApps;
#override
void initState() {
super.initState();
activateApps = getActivatedApps();
}
#override
Widget build(BuildContext context) {
IconData iconData = Icons.grid_view;
void _toggleViewIcon() {
setState(() {
if (iconData == Icons.grid_view) {
iconData = Icons.list;
} else {
iconData = Icons.grid_view;
}
});
}
return Scaffold(
appBar: AppBar(
title: const Text('Apps'),
primary: false,
toolbarHeight: 50,
leading: IconButton(
tooltip: 'Toggle view',
icon: Icon(iconData),
onPressed: _toggleViewIcon,
),
actions: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: IconButton(
tooltip: 'Refresh',
icon: const Icon(Icons.refresh),
onPressed: () {
setState(() {
activateApps = getActivatedApps();
});
},
)),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: IconButton(
tooltip: 'Add or remove apps',
icon: const Icon(Icons.add),
onPressed: () {
setState(() {
activateApps = getActivatedApps();
});
},
)),
],
),
primary: false,
body: Column(children: []));
}
}
The button never toggles. I am not sure what I am doing wrong here. Any help is appreciated.
setState will rebuild the widget, i.e the build function will be called again, thus the iconData variable will be set again to Icons.grid_view
move the iconData declaration and the function _toggleViewIcon outside of the build function
iconData variable will be set again to Icons.grid_view each time when you call set state if it put inside build method, so declare it outside build method
class _AppsScreenState extends State<AppsScreen> {
late Future<AllApps> activateApps;
IconData iconData = Icons.grid_view;
#override
void initState() {
super.initState();
activateApps = getActivatedApps();
}
#override
Widget build(BuildContext context) {
void _toggleViewIcon() {
setState(() {
if (iconData == Icons.grid_view) {
iconData = Icons.list;
} else {
iconData = Icons.grid_view;
}
});
}
return Scaffold(
appBar: AppBar(
title: const Text('Apps'),
primary: false,
toolbarHeight: 50,
leading: IconButton(
tooltip: 'Toggle view',
icon: Icon(iconData),
onPressed: _toggleViewIcon,
),
actions: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: IconButton(
tooltip: 'Refresh',
icon: const Icon(Icons.refresh),
onPressed: () {
setState(() {
activateApps = getActivatedApps();
});
},
)),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: IconButton(
tooltip: 'Add or remove apps',
icon: const Icon(Icons.add),
onPressed: () {
setState(() {
activateApps = getActivatedApps();
});
},
)),
],
),
primary: false,
body: Column(children: []));
}
}
You can use SteamBuilder instead, if you want to change a bit of space, but it will rebuild all widgets

How to solve bottom navigation bar missing when navigate to other page in flutter

I am using Flutter. I have bottom navigation with 3 tabs. When I use Navigator.push() to tabs 2 but the bottom navigation missing. How to solve this problem?
Here is my tabController.dart:
class _TabsControllerState extends State<TabsController> {
final List<Widget> pages = [
HomePage(
key: PageStorageKey('page1'),
),
CartPage(
key: PageStorageKey('page2'),
),
ProfilePage(
key: PageStorageKey('page3'),
),
];
final PageStorageBucket bucket = PageStorageBucket();
var _selectedIndex = 0;
Widget _bottomNavigationBar(var selectedIndex) => BottomNavigationBar(
onTap: (var index) => setState(() => _selectedIndex = index),
currentIndex: selectedIndex,
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(
icon: Icon(Icons.lightbulb_outline), title: Text('Cart')),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle), title: Text('Profile')),
],
);
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: ()async{
if(_selectedIndex == 0)
return true;
else setState(() {
_selectedIndex = 0;
});
return false;
},
child: Scaffold(
bottomNavigationBar: _bottomNavigationBar(_selectedIndex),
body: PageStorage(
child: pages[_selectedIndex],
bucket: bucket,
),
));
}
}
In CartPage I have details page. In Details page, when I press button I want go to HomePage. How can I do that?
Here is my code:
new FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => HomePage()));
},
),
If you look at the example on the Flutter's API documentation on the BottomNavigationBar, on the "Sample in App" Tab of the example, you will see that it shows you that the content that is being controlled by the BottomNavigationBar is declared above in the same class. You should do the same.
Remember you can simply have your Widgets in other files, import them, and instantiate them to use as each of the Tabs.
UPDATED
Here is an explanation of how to implement it on your code:
In your main class declare a method to change the Tab index that you can use and pass down to other classes:
void setTabIndex(index){
setState((){
_selectedIndex = index;
});
}
In your CartPage class, declare a variable on the constructor for the Function type:
Function setTabIndex;
Your button would like this:
FlatButton(
child: new Text("OK"),
onPressed: () {
setTabIndex(0);
// 0 is the position of your HomePage in your pages List
},
),

flutter setstate rebuild only one child

in flutter I need that when I call setstate, it only rebuilds a widget
I put 2 children in a stack, I need that when a button is pressed, only the second one is rebuilt.
bool popup = false;
Scaffold(
appBar: AppBar(
title: const Text('TEST'),
actions: <Widget>[
IconButton( // + BUTTON
icon: Icon(Icons.add),
onPressed: () {
setState(() {
popup = true;
});
},
),
IconButton( // - BUTTON
icon: Icon(Icons.remove),
onPressed: () {
setState(() {
popup = false;
});
),
],
),
body: SafeArea(
child: Stack(
children: <Widget>[
Container( // FIRST WIDGET
key: ValueKey(1),
child: Text("Random - "+new Random().nextInt(20).toString())
),
popup ? Center(child: Text("abc")) : Text("") , // SECOND WIDGET
],
),
),
);
I expect that when I press the "+" button only the second widget will be re-built, but now it will rebuild all the contents of the stack.
thank you all.
From the official docs we can read:
"When setState() is called on a State, all descendent widgets rebuild. Therefore, localize the setState() call to the part of the subtree whose UI actually needs to change. Avoid calling setState() high up in the tree if the change is contained to a small part of the tree."
My suggestion, and I use it most of the times, is separate the widget that you want to rebuild in a new StatefulWidget. This way the setState only will be rebuild that widget.
class MyAppBar extends StatefulWidget
...
class _MyAppBarState extends State<MyAppBar> {
bool popup = false;
#override
Widget build(BuildContext context) {
return AppBar(
title: const Text('TEST'),
actions: <Widget>[
IconButton( // + BUTTON
icon: Icon(Icons.add),
onPressed: () {
setState(() {
popup = true;
});
},
),
IconButton( // - BUTTON
icon: Icon(Icons.remove),
onPressed: () {
setState(() {
popup = false;
});
),
],
),
}
Then call it in your Scaffold:
Scaffold(
appBar: MyAppBar(),
Other method I can suggest is using ValueNotifier or notifyListeners(). Please read this page Avoid rebuilding all the widgets repetitively. It is well explained.
Another option is to use ValueListenableBuilder:
class _MyHomePageState extends State<MyHomePage> {
final ValueNotifier<bool> popup = ValueNotifier<bool>(false);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TEST'),
actions: <Widget>[
IconButton(
// + BUTTON
icon: Icon(Icons.add),
onPressed: () {
popup.value = true;
}),
IconButton(
// - BUTTON
icon: Icon(Icons.remove),
onPressed: () {
popup.value = false;
})
],
),
body: Center(
child: ValueListenableBuilder<bool>(
valueListenable: popup,
builder: (context, value, _) {
return Stack(
children: [
Text("Random - " + new Random().nextInt(20).toString()),
popup.value ? Center(child: Text("abc")) : Text(""),
],
);
}),
),
);
}
}
You can use StreamBuilder:
StreamController<bool> popup = StreamController<bool>();
#override
void dispose() {
popup.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TEST'),
actions: <Widget>[
IconButton( // + BUTTON
icon: Icon(Icons.add),
onPressed: () => popup.add(true),
),
IconButton( // - BUTTON
icon: Icon(Icons.remove),
onPressed: () => popup.add(false),
),
],
),
body: SafeArea(
child: Stack(
children: <Widget>[
Container( // FIRST WIDGET
key: ValueKey(1),
child: Text("Random - "+new Random().nextInt(20).toString())
),
StreamBuilder<bool>(
stream: popup.stream,
initialData: false,
builder: (cxt, snapshot) {
return snapshot.data ? Center(child: Text("abc")) : Text("");
},
)
],
),
),
);
}
Remove the setState from the widget you don't want to be changed. And only use setState for the ones you need to rebuild
Or you can consider using inheritedModel widget
Here is the example from where you can learn how to build an Inherited model widget to update only specific widgets rather than the whole widgets.
https://medium.com/flutter-community/flutter-state-management-setstate-fn-is-the-easiest-and-the-most-powerful-44703c97f035

How to change a text style on Flutter when button pressed

Is anyone know how to change a text style on Flutter when the button pressed?
As example, i have a code like this :
class _scanningState extends State<scan> {
String strText = 'ABCDEFG';
#override
Widget build(BuildContext context) {
return Scaffold(backgroundColor: Colors.blue,
body: new Column(
children: <Widget>[
new Text(strText),
new RaisedButton(
child: new Text('Button'),
onPressed: (){
//Change text style of strText()???
},
)
],
)
);
}
class _scanningState extends State<scanning> {
bool pressed = true;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
body: new Column(
children: <Widget>[
new Text(strText,
style: pressed
? TextStyle(color: Colors.black)
: TextStyle(color:Colors.green),
),
new RaisedButton(
child: new Text(
'Change color'),
onPressed: () {
setState(() {
pressed = !pressed;
});
},
)
],
));
}
Maybe you want to change the sibling text. The concept is the same. Happy Flutter
I recommend you to read about Stateful Widgets https://flutter.io/tutorials/interactive/#stateful-stateless
class _scanningState extends State<scanning> {
bool pressed = false;
String strText = 'ABCDEFG';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
body: new Column(
children: <Widget>[
new Text(strText),
new RaisedButton(
child: new Text(
'Button',
style: pressed
? TextStyle(fontSize: 30.0)
: TextStyle(fontSize: 10.0),
),
onPressed: () {
//Change text style of strText()???
setState(() {
pressed = !pressed;
});
},
)
],
));
}