SetState not updating listview - flutter

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!
});
}

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!

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)

Method setState() is not updating the UI (Flutter)

My home screen is a Scaffold with a ListView at its body and a floating action button at the bottom. The action button takes the user to a second screen, where he can type a text into a text input and press save. The save button calls a method at the home screen that adds the text to the List variable over which ListView is based. The problem is: the List variable is being updated (I can see on the log), but the setState is not updating the ListView. What am I doing wrong?
Here's the code from the Home Screen:
import 'package:flutter/material.dart';
import 'addCounter.dart';
class Home extends StatefulWidget {
#override
HomeState createState() => HomeState();
}
class HomeState extends State<Home> {
List lista = <Widget>[];
void incrementLista(newItem) {
print('$lista');
setState(() {
lista.add(Box('newItem'));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List of Counters'),
backgroundColor: Colors.deepPurple[1000],
),
body: Builder(
builder: (context)=>
Center(
child: ListView(children: lista),
),),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.grey[1000],
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddCounter(f: incrementLista)));
},
child: Icon(Icons.add, color: Colors.white)),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomNavigationBar(
unselectedItemColor: Colors.grey[700],
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.list),
title: Text('Lista'),
),
BottomNavigationBarItem(
icon: Icon(Icons.insert_chart),
title: Text('Gráfico'),
),
],
selectedItemColor: Colors.blue,
),
);
}
}
And here is the code from the addCounter.dart:
import 'package:flutter/material.dart';
class AddCounter extends StatefulWidget {
final Function f;
AddCounter({#required this.f});
#override
_AddCounterState createState() => _AddCounterState();
}
class _AddCounterState extends State<AddCounter> {
final myController = TextEditingController();
#override
void dispose() {
myController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Add a counter'),
backgroundColor: Colors.blue,
),
body: Column(children: [
Padding(
padding: EdgeInsets.all(15),
child: TextField(
controller: myController,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 2)),
hintText: 'Type a name for the counter'),
),
),
RaisedButton(
color: Colors.green,
onPressed: () {
widget.f(myController.text);
Navigator.pop(context);
},
child: Text(
'Save',
style: TextStyle(color: Colors.white),
),
)
]));
}
}
I don't think the code for the Box widget is relevant. It is basically a card with a title.
#pskink gave me an answer that worked perfectly. Here it is:
basically you should not use such a list of widgets, data and presentation layers should be separated, instead you should use a list of data only, see https://flutter.dev/docs/cookbook/gestures/dismissible for more info

Flutter: SnackBar inside Dismissible widget is not working properly

My Home Screen is a Scaffold with a list of Dismissible widgets at its body. The child of each Dismissible is a custom widget I created, named Box. I have a FloatingActionButton that takes me to a new screen where I can add more Boxes to the list. (Each Box receive a String as an argument, that is used as its title).
I want the method onDismissed to show a SnackBar with the text "(Title of the box) excluded". However, this is not working as expected. The problem is that it always displays the title of the last box included, and not the title of the one I just dismissed. For example, if I add a Box named "Box 1", and then a Box named "Box 2", and then I dismiss the "Box 1", the snackbar will say "Box 2 excluded".
What am I doing wrong?
Here is the relevant code:
import 'package:flutter/material.dart';
import 'box.dart';
import 'addCounter.dart';
class Home extends StatefulWidget {
#override
HomeState createState() => HomeState();
}
class HomeState extends State<Home> {
List<String> lista;
void incrementLista(String novoItem) {
print('$lista');
setState(() {
lista.add(novoItem);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Counters'),
backgroundColor: Colors.black,
),
body: ListView.builder(
itemCount: lista.length,
itemBuilder: (context, index) {
return Dismissible(
background: Container(color: Colors.grey[800], child: Icon(Icons.delete, color: Colors.grey[100])),
key: Key(lista[index]),
onDismissed: (direction) {
setState(() {
lista.removeAt(index);
});
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(lista[index] + ' excluded.')));
},
child: Box(lista[index]));
},
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.grey[1000],
onPressed: () {
//Navega para tela de adicionar tarefa
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddCounter(f: incrementLista)));
},
child: Icon(Icons.add, color: Colors.white)),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomNavigationBar(
//backgroundColor: Colors.grey[50],
unselectedItemColor: Colors.grey[700],
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.list),
title: Text('List'),
),
BottomNavigationBarItem(
icon: Icon(Icons.insert_chart),
title: Text('Chart'),
),
],
// currentIndex: _selectedIndex,
selectedItemColor: Colors.blue,
//onTap: _onItemTapped,
),
);
}
}
And here is the code of the addCounter.dart:
import 'package:flutter/material.dart';
class AddCounter extends StatefulWidget {
final Function f;
AddCounter({#required this.f});
#override
_AddCounterState createState() => _AddCounterState();
}
class _AddCounterState extends State<AddCounter> {
final myController = TextEditingController();
#override
void dispose() {
myController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Add a counter'),
backgroundColor: Colors.blue,
),
body: Column(children: [
Padding(
padding: EdgeInsets.all(15),
child: TextField(
controller: myController,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 2)),
hintText: 'Type a name for the counter'),
),
),
RaisedButton(
color: Colors.green,
onPressed: () {
widget.f(myController.text);
Navigator.pop(context);
},
child: Text(
'Save',
style: TextStyle(color: Colors.white),
),
)
]));
}
}
I think the problem is that you first remove the item of the list. When you show the SnackBar the item is already removed so you can't access it in the list. So I would suggest to first show the Snackbar and then remove the item.
Like this: (In your itemBuilder)
return Dismissible(
background: Container(color: Colors.grey[800], child: Icon(Icons.delete,
color: Colors.grey[100])),
key: Key(lista[index]),
onDismissed: (direction) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(lista[index] + ' excluded.')));
setState(() {
lista.removeAt(index);
});
},
child: Box(lista[index]));
},

I want to pass data from 2nd Screen to 1st Screen in flutter

I am making a simple TODO app and I wanted to pass data from 2nd Screen of my app to 1st screen. My first Screen is initially blank and there is nothing to display and it has a floating button to add a task. When it is clicked it takes to the Second page where user inputs the task and author and Clicks on "Submit" Button and takes us to 1st page where it gets Displayed. I want to pass data as List .I am trying everything for last 24 hours I implemented using ModalRoute and also created one instance of ToDo class so that it doesn't give NULL error but nothing is working out. I am attaching code So that you can understand my problem.
This is my FirstScreen()
import 'package:flutter/material.dart';
import 'todo.dart';
import 'todocard.dart';
class ToDos extends StatefulWidget{
#override
_ToDosState createState() => _ToDosState();
}
class _ToDosState extends State<ToDos> {
#override
Widget build(BuildContext context) {
List<ToDo> todos =[
];
final routeArgs = ModalRoute.of(context).settings.arguments as Map ;
todos.add(ToDo(author: routeArgs['task'],task: routeArgs['author']));
return Container(
child: Scaffold(
appBar: AppBar(
title: Text("TODO LIST"),
centerTitle: true,
),
body: Column(
children:todos.map((e) => ToDoCard(
todo: e,
)).toList(),
//ToDoCard is just a Card widget
),
floatingActionButton: FloatingActionButton(
elevation: 0.0,
child: Text("+"),
onPressed: ()
{
Navigator.pushNamed(context, '/add_task');
},
),
),
);
}
}
My SecondScreen is :
import 'package:flutter/material.dart';
class AddTask extends StatefulWidget {
#override
_AddTaskState createState() => _AddTaskState();
}
class _AddTaskState extends State<AddTask> {
#override
Widget build(BuildContext context) {
String author,task;
return Container(
child: Scaffold(
appBar: AppBar(
title: Text("ADD TASK"),
centerTitle: true,
),
body: Column(
children: <Widget>[
Text("Enter Your Task"),
TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'task'
),
onChanged: (text){
task = text;
},
),
TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'author'
),
onChanged: (text){
author = text;
},
),
Row(
children: <Widget>[
RaisedButton(
onPressed: () {
Navigator.pop(context, {
'author': author,
'task': task,
});
},
child: Text("Submit"),
),
SizedBox(width: 10.0,),
RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text("Cancel"),
),
],
)
],
),
));
}
}
The main.dart is as Follows:
import 'package:flutter/material.dart';
import 'todo.dart';
import 'add_task.dart';
import 'display_todo.dart';
void main() {
runApp(MaterialApp(
title: 'Passing Data',
initialRoute: '/',
routes: {
'/': (context) => ToDos(),
'/add_task': (context) => AddTask(),
},
));
}
The ToDoCard for displaying the info as Card:
import 'todo.dart';
import 'package:flutter/material.dart';
class ToDoCard extends StatelessWidget {
final ToDo todo;
ToDoCard({this.todo});
#override
Widget build(BuildContext context) {
return Card(
color: Colors.cyan,
margin: EdgeInsets.fromLTRB(20, 20, 20, 0),
child: Padding(
padding: EdgeInsets.fromLTRB(13, 10, 13, 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
todo.author,
style: TextStyle(
color: Colors.black,
fontSize: 20.0,
),
),
SizedBox(height: 10.0,),
Text(
todo.task,
style: TextStyle(
color: Colors.black,
fontSize: 20.0,
),
),
SizedBox(height: 10.0,),
// RaisedButton.icon(onPressed: delete, icon: Icon(Icons.delete), label:
Text("Delete quote"), color: Colors.red,),
],
),
),
);
}
}
ToDo class:
class ToDo{
final String task;
final String author;
ToDo({this.task,this.author});
}
You can pass the result back on the Navigator.pop() and retrieve it by awaiting the pushNamed call.
Retrieve value in Page 1:
onPressed: () async
{
dynamic result = await Navigator.pushNamed(context, '/add_task');
if(result != null) {
setState(() {todos.add(result);});
}
},
Pass value from page 2 in the submit button
onPressed: () {
Navigator.pop(context, ToDo(task: task, author: author));
},