IconButton selectedIcon not toggling - flutter

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!

Related

How to keep drawer always open

I want to put a drawer like this in my Flutter app:
just like https://m3.material.io/develop/flutter
I'm using NavigationRail and it's said that a menu button can be added to open a navigation drawer. Does any knows how to add the menu button and the drawer?
menu button of NavigationRail
thanks.
It's a bit hard to use a regular Scaffold Drawer without the regular scaffold controls, as far as I can tell.
I came up with a solution for your problem, if I understood it correctly. Looks a lot like the spec site, needs a bit of styling.
Took the example from the NavigationRail documentation and added a Visibility widget. Now clicking on the destinations, you can show and hide their child widgets(drawer). No drawer animation though.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true),
home: const NavRailExample(),
);
}
}
class NavRailExample extends StatefulWidget {
const NavRailExample({super.key});
#override
State<NavRailExample> createState() => _NavRailExampleState();
}
class _NavRailExampleState extends State<NavRailExample> {
int _selectedIndex = 0;
NavigationRailLabelType labelType = NavigationRailLabelType.all;
bool showLeading = false;
bool showTrailing = false;
double groupAligment = -1.0;
bool _isClosed = false;
Widget _getWidget(int index) {
switch (index) {
case 1:
return GestureDetector(
child: const Text('Tap!'),
onTap: () => setState(() {
_isClosed = true;
}),
);
case 2:
return const Text('empty');
default:
return ListView(
children: const [
ExpansionTile(
title: Text('whatev'),
children: [Text('1'), Text('2')],
),
ListTile(
title: Text('adfafdafaf'),
)
],
);
}
}
Widget _getPage(int index) {
switch (index) {
case 1:
return const Center(child: Text('sheeesh'));
case 2:
return const Center(child: Text('empty'));
default:
return const Center(child: Text('yolo'),);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: <Widget>[
NavigationRail(
selectedIndex: _selectedIndex,
groupAlignment: groupAligment,
onDestinationSelected: (int index) {
setState(() {
_isClosed = (_selectedIndex == index || _isClosed)
? !_isClosed
: _isClosed;
_selectedIndex = index;
});
},
labelType: labelType,
leading: showLeading
? FloatingActionButton(
elevation: 0,
onPressed: () {
// Add your onPressed code here!
},
child: const Icon(Icons.add),
)
: const SizedBox(),
trailing: showTrailing
? IconButton(
onPressed: () {
// Add your onPressed code here!
},
icon: const Icon(Icons.more_horiz_rounded),
)
: const SizedBox(),
destinations: const <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('First'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.book),
label: Text('Second'),
),
NavigationRailDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: Text('Third'),
),
],
),
Visibility(
maintainState: false,
visible: !_isClosed,
child: Row(
children: [
const VerticalDivider(thickness: 1, width: 1),
SizedBox(
height: double.infinity,
width: 200,
child: _getWidget(_selectedIndex),
)
],
),
),
const VerticalDivider(thickness: 1, width: 1),
// This is the main content.
Expanded(
child: _getPage(_selectedIndex),
),
],
),
),
);
}
}

How would I navigate to a new route inside a list widget? (Line 41: context,) is where the issue occurs

import 'dart:js';
import 'package:google_fonts/google_fonts.dart';
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
// ignore: prefer_final_fields
static List<Widget> _widgetOptions = <Widget>[
Container(
child: ElevatedButton(
child: const Text('Open route'),
onPressed: () {
Navigator.push(
**This is where the error occurs: I am unsure of how to access or use context within list widgets**
context, // ********THIS IS WHERE THE ERROR IS******
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
},
),
),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('OBTAIN'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
drawer: Drawer(
// Add a ListView to the drawer. This ensures the user can scroll
// through the options in the drawer if there isn't enough vertical
// space to fit everything.
child: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text('Drawer Header'),
),
ListTile(
title: const Text('Item 1'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
ListTile(
title: const Text('Item 2'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
],
),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
class SecondRoute extends StatelessWidget {
const SecondRoute({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Route'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go back!'),
),
),
);
}
}
How would I navigate to a new route inside a list widget? (Line 41: context,) is where the issue occurs.
The issue is you are trying to get context direct under state class. There is trick can be done using late keyword or do inside initState. (You can't use static)
Also avoid using context like this when ever possible.
"It breaks the context chain, which means you're violating the build system's contract." - And this variable wont be responde to update untill you reintalize this variable inisde setState.
late List<Widget> _widgetOptions = <Widget>[
Container(
child: ElevatedButton(
child: const Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
},
),
),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];

SetState not updating listview

Im trying to make it so when you press search it creates a listtile. It works but for it to work I have to click the button and then rebuild the app for it to appear. I was looking at some other posts but I could not find anything that worked. The main parts to look at is I have a function that adds a listtile. I have a button with an on press to create the tile. And I have the children of container at the bottom as the list of created listtiles.
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<Widget> _listOfWidgets = [];
#override
Widget build(BuildContext context) {
_addItemToList() {
List<Widget> tempList =
_listOfWidgets; // defining a new temporary list which will be equal to our other list
tempList.add(ListTile(
key: UniqueKey(),
leading: Icon(Icons.list),
trailing: FlatButton(
onPressed: () async {},
//Download Link
child: Text(
"Download",
style: TextStyle(color: Colors.green, fontSize: 15),
),
),
title: Text("")));
this.setState(() {
_listOfWidgets =
tempList; // this will trigger a rebuild of the ENTIRE widget, therefore adding our new item to the list!
});
}
return MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: currentTheme.currentTheme(),
home: Scaffold(
appBar: AppBar(
actions: [
IconButton(
onPressed: () {
setState(() {
currentTheme.switchTheme();
});
},
icon: Icon(Icons.wb_sunny),
),
IconButton(
onPressed: () async {
await FirebaseAuth.instance.signOut();
},
icon: Icon(Icons.exit_to_app),
),
],
backgroundColor: Colors.blue,
title: Text("Home"),
),
body: ListView(
children: [
ListTile(
leading: SizedBox(
width: 300,
child: TextField(
controller: search,
decoration: InputDecoration(
labelText: "Enter Manga Name",
),
),
),
trailing: ElevatedButton(
onPressed: () async {
_addItemToList();
},
child: Text("Search")),
),
Container(
margin: EdgeInsets.all(15),
width: 100,
height: 515,
color: Colors.black12,
child: ListView(
children: _listOfWidgets,
))
],
)));
}
}
try add below code
if you update status you need setState()
A better way to state management is to use a BLoC or Provider package.
...
onPressed: () {
setState(() {
_addItemToList();
});
},
...
Figured it out after a bit of tinkering. Fix for me was to add key: UniqueKey(), to my ListView. I had keys added to my ListTiles instead of the actual ListView.
onPressed: () {
setState(() {
_addItemToList();
});
},
The Problem Solution is :
List<Widget> tempList = <Widget>[];
_addItemToList() {
tempList.addAll(
_listOfWidgets); // defining a new temporary list which will be equal to our other list
tempList.add(ListTile(
key: UniqueKey(),
leading: Icon(Icons.list),
trailing: FlatButton(
onPressed: () async {},
//Download Link
child: Text(
"Download",
style: TextStyle(color: Colors.green, fontSize: 15),
),
),
title: Text("")));
this.setState(() {
_listOfWidgets =
tempList; // this will trigger a rebuild of the ENTIRE widget, therefore adding our new item to the list!
});
}

How to show correct Icon in IconButton 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(),
],

Refactoring Dart Code into a separate file

I have this code where I have the sidebar (drawer) in my app. I have created a separate file drawer.dart which looks somewhat like this :
import 'package:flutter/material.dart';
class DrawerClass extends StatefulWidget {
#override
_DrawerClassState createState() => _DrawerClassState();
}
class _DrawerClassState extends State<DrawerClass> {
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
DrawerHeader(
child: CircleAvatar(
backgroundColor: Theme.of(context).primaryColor,
radius: 30,
child: Icon(
Icons.pets,
size: 60,
color: Colors.white,
),
),
),
ListTile(
leading: Icon(Icons.home),
title: Text("Home"),
onTap: () {
Navigator.pushReplacementNamed(context, "/home");
},
),
ListTile(
leading: Icon(
Icons.report,
),
title: Text("Report"),
onTap: () {
Navigator.pushReplacementNamed(context, "/report");
},
),
ListTile(
leading: Icon(
Icons.settings,
),
title: Text("Settings"),
onTap: () {
Navigator.pop(context);
},
),
],
),
);
}
}
Now I have three different files: home.dart , report.dart, settings.dart. This refactored code will work perfectly when used in settings.dart but not in other two files. Example, if I use this in report.dart I'll have to change it's onTap status to Navigator.pop. I need to use drawer.dart in all the other 3 files changing only the onTap status in every file.
Any help will be appreciated:)
One way to do this is like so.
enum ScreenName {
Home,
Report,
Settings,
}
Use this enum as a property (parentScreen) of DrawerClass so it knows which screen it is in.
class DrawerClass extends StatefulWidget {
final ScreenName parentScreen;
const DrawerClass({#required this.parentScreen});
#override
_DrawerClassState createState() => _DrawerClassState();
}
class _DrawerClassState extends State<DrawerClass> {
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
// other children,
ListTile(
leading: Icon(Icons.home),
title: Text("Home"),
onTap: () {
if (widget.parentScreen == ScreenName.Home)
Navigator.pop(context);
else
Navigator.pushReplacementNamed(context, "/home");
},
),
ListTile(
leading: Icon(Icons.report),
title: Text("Report"),
onTap: () {
if (widget.parentScreen == ScreenName.Report)
Navigator.pop(context);
else
Navigator.pushReplacementNamed(context, "/report");
},
),
ListTile(
leading: Icon(Icons.settings),
title: Text("Settings"),
onTap: () {
if (widget.parentScreen == ScreenName.Settings)
Navigator.pop(context);
else
Navigator.pushReplacementNamed(context, "/settings");
},
),
],
),
);
}
}
Then use DrawerClass like so. For example, for setting screen -
DrawerClass(parentScreen: ScreenName.Settings)