Flutter: states not updated if child widget is list item - list

I have a List of Widgets as children of a Stuck.
When I call the setState the list widgets not update the value.
If I add the same widget in the Stack children as class (not as list item) the state is working.
On the following code there are two widgets in the Stack.
The second widget is a list item. This widgets not updates its value.
Why?
I want to have a Widget list on the Stuck and change the state of its children.
On my app I want to manage the widgets from the list. I don't want to add the widgets on the "children" code of the stuck because the widgets will be added dynamically.
`
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double _counter = 0;
List<Widget> list = [];
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
if (list.length==0)
list.add( Positioned( // this widget is added as list item
left: 100,
top: 100,
child: Text(
'$_counter', // this not changed after calling setState
style: Theme.of(context).textTheme.headline4,
),
),);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Stack(
children:
[Positioned( // this widget added as not a list item
left: 100,
top: 0,
child: Text(
'$_counter', // this state is changed every time the button is clicked. It is working
style: Theme.of(context).textTheme.headline4,
),
),
list[0]], // this is the same with above widget as list item. The state is not changed
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
`
The output of the code. The second widget can't be updated

if (list.length==0)
this line means don't add new item if list not empty
so the inside text will never change

This is Happening because you have added if (list.length==0) after the _counter is updated the second time the list.length is not zero. Either remove it or use some other method

The exact error here is that in line
List<Widget> list = [];
here you declare the variable which never redeclares on-call or setState
when the build method runs for the first time the list is empty i.e. it's length == 0
this adds a positioned widget item in the list the value until here for _counter variable is 0 hence the list contains the widget _counter value set to 0 and the list length is 1 now.
Now when setState is called what happens is that the build method runs and it does this check
if (list.length==0){
list.add(
Positioned(
left: 100,
top: 100,
child: Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
)
);
}
//which apparently fails as the list length is 1 due to the addition of positioned widget on the first call of build method.
thought you have updated the _counter variable this doesn't affect the 0th index element in the list as there is no such statement to update the list item.
if you really want to update the 0th list element in the list then add just this line at the top of your build method
//should look like this
Widget build(BuildContext context) {
list = [];
if (list.length==0)
list.add( Positioned( // this widget is added as list item
left: 100,
top: 100,
child: Text(
'$_counter', // this not changed after calling setState
style: Theme.of(context).textTheme.headline4,
),
),);
return Scaffold(....
doing so will get you both the outputs in the image added to be the same are with the latest _counter value

Related

How can i make a dynamic list in Flutter where user can add items through a textfield and delete the item again?

I want to have a textinputfield for the user and when he tips in something and clicks on a button the input should be shown in a list item. The user should also have the option to delete items of the list just like a in and todo app.
Here you can find my code:
Link to Code
So I decided to write this simple program just to freshen up my skills... You can directly copy paste this code and it should work just fine.
I have used the provider package here to make this a bit more professional, as you can't always rely on setState() to update your UI when tasks are added to your list. And also because you will probably be using the provider more often in the future.
I have added comments in the below code to make it easy to understand. However, do not hesitate to clear up any confusions in the comments :)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todo/list_provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider( // This initiates the provider.
create: (context) => TaskProvider(), // Initiating it here makes this provider data available everywhere in the application
child: MaterialApp(
title: 'Flutter Demo',
home: const MyHomePage(),
),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dynamic List'),
),
body: const HomePageBody(), // I pass a separate widget here just to make the code a bit cleaner
floatingActionButton: FloatingActionButton(
onPressed: () => showModalBottomSheet( // This calls a bottom Modal Sheet which pops up while pressing the floating action button
context: context, builder: (context) => const BottomSheet()),// The modal sheet displays the BottomSheet() Widget which I have defined down in this code.
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
// This is where the ListView will be shown
class HomePageBody extends StatelessWidget {
const HomePageBody({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
List<String> listOfTasks = Provider.of<TaskProvider>(context).getTasks; // This is where the list is being accessed from the Provider file.
return Container(
padding: const EdgeInsets.all(20),
child: ListView.builder(
itemCount: listOfTasks.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
listOfTasks[index],
),
);
},
),
);
}
}
// This is the BottomSheet Widget where I decided to take User Input from
class BottomSheet extends StatefulWidget {
const BottomSheet({Key? key}) : super(key: key);
#override
State<BottomSheet> createState() => _BottomSheetState();
}
class _BottomSheetState extends State<BottomSheet> {
String task = ''; // This variable holds the tasks user wants to add
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(
top: 20,
left: 20,
right: 20,
bottom: MediaQuery.of(context).viewInsets.bottom + 20, // viewInsets.bottom adds padding from the bottom to avoid keyboard overlapping textfield widget
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(// You can use TextField Widget as well
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
onChanged: (value) { // This saves the value in the TextField for every character the user types
task = value; // The value in the TextField is referred to by the 'value' variable
},
),
const SizedBox(
height: 10,
),
ElevatedButton(
onPressed: () => saveTask(task),
child: const Text('Save Task'),
),
],
),
);
}
void saveTask(String task) {
Provider.of<TaskProvider>(context, listen: false).addTasks(task); //This is where I am calling the function to add a task to the list.
// The 'addTasks()' function is defined in the provider file which is just below
}
}
Here's the list_provider.dart file that I import in above code:
import 'package:flutter/foundation.dart';
class TaskProvider extends ChangeNotifier { // This is the class where your data exists
// and this is the only place where your data should be manipulated! I explain the reason below...
final List<String> _tasks = [];
List<String> get getTasks { // We use a getter to retrieve the list
return _tasks; // We do that in order to avoid modifications to this list from any outside sources.
}
void addTasks(task) {
_tasks.add(task); // This is simply how you add anything to a list
notifyListeners(); // This is why we use providers. This function notifies all the children widgets
// of the Widget where we initiated our provider (see the parent of MaterialApp Widget in the above code)
// This is why changes to data should be made within this class only as it extends ChangeNotifier,
// which provides us with notifyListeners() method. Which ultimately notifies the widgets that the data has been modified and its time to rebuild the widgets that rely on this data!
}
}
You can copy paste this code, just make sure to add the provider package in your pubspec.yaml file as shown below.

how to bordered a single button on selected in list

I have a list of buttons,
I would like to select one of them and the red bordered appear onClick.
once click again, border disappear. How can I achieved that?
As far as I did, when I click it circle all the button in list at once.
please kindly teach me.
First you will have to create a stateful widget for your button, that keeps track of if it should show the border or not. This is what it should look like:
class MyButton extends StatefulWidget {
#override
_MyButtonState createState() => _MyButtonState();
final Widget child;
final void Function() onPressed;
final Key key;
final Color buttonPrimaryColor, buttonOnPrimaryColor, borderColor;
final double borderWidth;
MyButton({
#required this.child,
this.onPressed,
this.key,
this.borderColor = Colors.red,
this.buttonPrimaryColor,
this.buttonOnPrimaryColor,
this.borderWidth = 1.0,
});
}
class _MyButtonState extends State<MyButton> {
bool showBorder = false;
void toggleShowBorder() => setState(() => showBorder = !showBorder);
#override
Widget build(BuildContext context) {
return ElevatedButton(
key: widget.key,
onPressed: widget.onPressed == null
? null
: () {
toggleShowBorder();
widget.onPressed();
},
child: widget.child,
style: ButtonStyle(
// If showBorder == true, we show a red border
shape: MaterialStateProperty.resolveWith(
(_) => showBorder
? RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
side: BorderSide(
color: widget.borderColor,
width: widget.borderWidth,
),
)
: RoundedRectangleBorder(),
),
).merge(
ElevatedButton.styleFrom(
primary: widget.buttonPrimaryColor ??
Theme.of(context).buttonTheme.colorScheme.primary,
onPrimary: widget.buttonOnPrimaryColor ??
Theme.of(context).buttonTheme.colorScheme.onPrimary,
),
),
);
}
}
The usage is very simple:
MyButton(
child: Text('Press me'),
onPressed: () {},
),
If you want to change the button's color you can do it by passing in parameters:
MyButton(
borderColor: Colors.amber,
buttonPrimaryColor: Colors.white,
buttonOnPrimaryColor: Colors.black,
child: Text('Press me'),
onPressed: () {},
),
Use buttonPrimaryColor for the button's background color
Use buttonOnPrimaryColor for the button's foreground color
Use borderColor for the button's border color (it is red by default)
You can also change the border width, using the borderWidth parameter, like this: (it is 1.0 by default)
MyButton(
borderWidth: 4.0,
child: Text('Press me'),
onPressed: () {},
),
It would also be a good idea to pass in a key parameter since you are working
with a list of buttons, like this:
MyButton(
key: Key('some unique key'),
child: Text('Press me'),
onPressed: () {},
),
The key should be a unique string.

Manage items in a List with their ids

Let me explain, I have two List Views with Items. At the moment I don’t give an id to these items but I have a problem. In fact, when I remove an item from my favorites list, it doesn’t change the icon (favorite or not favorite) for the right item on the home_screen.
I want to get the place of the item in the menu screen so I can change the icon from the favorites list. I’m using the provider package.
And so I wonder if it wouldn’t be better to create an id for each item and store a List<int> and then create a List<Item> in my favorites list. Also, I can use this id to change the right icon.
But I don’t know how to use these ids to create a List and then change the right icon.
Illustrations of what I said :
Black heart = in favorite and White heart = not in favorite.
It is the wrong item which is deleting.
My code on Github ans some relevant parts of my code :
favModel.dart
class FavModel extends ChangeNotifier {
List<Item> favList = [];
List<bool> isInFav = [];
addInFavorite(title, description, index){
Item item = Item(title: title, description: description, );
favList.add(item);
isInFav[index] = true;
notifyListeners();
}
removeOfFavorite(int index, int index2){
favList.removeAt(index);
isInFav[index2] = false;
notifyListeners();
}
implement(){
isInFav.add(false);
}
}
favorite_screen.dart
class Favorite extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Favorite'),
),
body: Consumer<FavModel>(
builder: (context, favModel, child) {
return ListView.builder(
itemCount: favModel.favList.length,
itemBuilder: (context, index) {
return TextObject(favModel.favList[index].title,
favModel.favList[index].description),
Padding(
padding: const EdgeInsets.all(7.0),
child: GestureDetector(
child: Icon(
Icons.favorite,
color: Colors.red,
size: 32,
),
onTap: () {
favModel.removeOfFavorite(index, index);
}),
),
});
},
),
);
}
}
home_screen.dart
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
actions: [
IconButton(
icon: Icon(Icons.favorite_border),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) {
return Favorite();
},
),
),
),
],
),
body: Consumer<FavModel>(builder: (context, favModel, child) {
return ListView.builder(
shrinkWrap: false,
itemCount: itemData.length,
itemBuilder: (context, index) {
favModel.implement();
return TextObject(
itemData[index].title, itemData[index].description),
Padding(
padding: const EdgeInsets.all(7.0),
child: GestureDetector(
child: Icon(
favModel.isInFav.elementAt(index)
? Icons.favorite
: Icons.favorite_border,
color:
favModel.isInFav[index] ? Colors.red : null,
size: 32,
),
onTap: () {
favModel.isInFav[index]
? null
: Provider.of<FavModel>(context,
listen: false)
.addInFavorite(
itemData[index].title,
itemData[index].description,
index,
);
}),
);
});
}),
);
}
}
Where I want to get the index is in the favorite_screen.dart at this line favModel.removeOfFavorite(index, index);
I would suggest you to add bool isFavorite to your class Item and add an id for the class also. So you can avoid having two arrays.
And using the id will help you using some awesome methods like findWhere and removeWhere
EDIT
You can iterate the List using for
for(int i = 0;i<favList.length;i++){
if(favList[i].id == selectedItem.id){
favList[i].isSelected = true;
break;// break the loop no need to continue
}
}
notifyListeners()
Notice that now you have to pass Item instead of index

Flutter list of Text: How to fix the style of all list items

I am new to flutter and to the concept of Object orientation in general. I am building a list of Text to be used with a CupertinoPicker in flutter, I want to use the same style for all the list items but I don't want to keep repeating the lines and each time specifying the text style.
For example, see the list of car manufacturers below:
import 'package:flutter/material.dart';
TextStyle kStyle = TextStyle(color: Colors.white, fontWeight: FontWeight.w900);
List<Text> manufacturers = [
Text('Toyota', style: kStyle,),
Text('VolksWagen', style: kStyle,),
Text('Nissan', style: kStyle,),
Text('Renault', style: kStyle,),
Text('Mercedes', style: kStyle,),
Text('BMW', style: kStyle,)
];
You see the list items in manufacturers list can get so long with more cars, can I use a class to tell flutter that my style is fixed to kstyle for all the items without explicitly writing style: kstyle for every single line?
Basically We can use DefaultTextStyle widget
Final Result
CupertinoPicker Widget
Common Column Widget
1. The Problem is we need to use CupertinoPicker
which in the library, it is defined as
final Widget result = DefaultTextStyle(
style: CupertinoTheme.of(context).textTheme.pickerTextStyle,
child: Stack(
2. Solution : Override Theme
Therefore We are required to defined its style at the very beginning definition of our app
const TextStyle kStyle = TextStyle(
color: Colors.blue,
fontWeight: FontWeight.w900,
);
class FlutterApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Cupertino Picker',
home: ListViewScreen(),
theme: ThemeData(
cupertinoOverrideTheme: CupertinoThemeData( // <---------- this
textTheme: CupertinoTextThemeData(
pickerTextStyle: kStyle,
),
),
),
);
}
}
A. Full Working Code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(FlutterApp());
}
const TextStyle kStyle = TextStyle(
color: Colors.blue,
fontWeight: FontWeight.w900,
);
class FlutterApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Cupertino Picker',
home: ListViewScreen(),
theme: ThemeData(
cupertinoOverrideTheme: CupertinoThemeData( // <---------- this
textTheme: CupertinoTextThemeData(
pickerTextStyle: kStyle,
),
),
),
);
}
}
class ListViewScreen extends StatelessWidget {
final List<Text> manufacturers = [
Text('Toyota'),
Text('VolksWagen'),
Text('Nissan'),
Text('Renault'),
Text('Mercedes'),
Text('BMW')
];
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text("Select Car"),
),
child: Container(
height: 200,
child: CupertinoPicker(
itemExtent: 50,
onSelectedItemChanged: (int index) {
print(index);
},
children: manufacturers,
),
),
);
}
}
B. [Optional] Simple use of Default Text Style
List<Text> manufacturers = [
Text('Toyota'),
Text('VolksWagen'),
Text('Nissan'),
Text('Renault'),
Text('Mercedes'),
Text('BMW')
];
const TextStyle kStyle = TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
);
class CarList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: kStyle,
child: Column(
children: manufacturers,
),
);
}
}
You can create a new widget:
class MyTextWidget extends StatelessWidget {
final String text;
const MyTextWidget({Key key, this.text}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text(text,style: TextStyle(color: Colors.white, fontWeight: FontWeight.w900),);
}
}
and use it in your list
List<Text> manufacturers = [
MyTextWidget('Toyota'),
MyTextWidget('VolksWagen'),
MyTextWidget('Nissan'),
MyTextWidget('Renault'),
MyTextWidget('Mercedes'),
MyTextWidget('BMW')
];
If you want to change the font in all the application, you must change it from MaterialApp like this:
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
textTheme: TextTheme(
//Use the appropriate TextStyle
),
),);
}
You can create one method for all Text widgets:
Text styledText(String text) => Text(text, style: TextStyle(color: Colors.white, fontWeight: FontWeight.w900));
and use it in your list:
List<Text> manufacturers = [
styledText('Toyota'),
styledText('VolksWagen'),
styledText('Nissan'),
styledText('Renault'),
styledText('Mercedes'),
styledText('BMW')
];
You can also create a list of strings
List<String> manufacturers = [
'Toyota',
'VolksWagen',
'Nissan',
'Renault',
'Mercedes',
'BMW',
];
And use tis method when you iterate through your list.
Or you can create a class instead of the method:
class CustomStyledText extends StatelessWidget {
final String text;
const CustomStyledText(this.text, {Key key}) : super(key: key);
TextStyle get _style => TextStyle(color: Colors.white, fontWeight: FontWeight.w900);
#override
Widget build(BuildContext context) => Text(text, style: _style);
}
You can create an extension on the Text widget and use that:
Create an extension:
// extension
extension on Text {
// method to apply style
applyStyle(TextStyle textStyle) {
return Text(
this.data,
style: textStyle,
);
}
}
Use the extension method on the Text widget:
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
...List.generate(
manufacturers.length,
// call the applyStyle method on the Text widget
(index) => manufacturers[index].applyStyle(kStyle),
).toList(),
],
),
);

Flutter: getting switch toggle values from dynamic form or why does state change rebuild differs

I have kind of a form where I can add cards, each having 5 textfields and 2 switches. I would like to use a method to build the switch code (and the textfield code, but that is working). However, the switches refuse to show their intended state. I saw couple of similar questions. However, most were solved with a list view listing all switched/checkboxes next to one another (I have multiple cards with multiple textfields and multiple switches, each). This was close, but I don't really understand the answer (within the comments)
Actually some answers come up with the same (I guess more or less same because mine isn't working) code storing the switch state in a bool list. When debugging I can see that the values are correctly stored in the list. However, the changed value is not rendered upon state change.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
var descrTECs = <TextEditingController>[];
var fixedSCs = [true]; //storing the switch values
var cards = <Card>[]; // storing the list of cards with forms
SizedBox createTextField(String placeholderStr, double fieldWidth) {
var tFieldController = TextEditingController();
switch (placeholderStr) { //switching placeholder to assign text controller to correct controller list
case "Description":
descrTECs.add(tFieldController);
break;
}
return SizedBox(width: fieldWidth, height: 25,
child: CupertinoTextField(
placeholder: placeholderStr,
controller: tFieldController,
),
);
}
SizedBox createSwitch(int pos) {
return SizedBox(width: 50, height: 25,
child: CupertinoSwitch(
value: fixedSCs[pos],
onChanged: (value) {
setState(() => fixedSCs[pos] = value); // value is stored in fixedSCs but not rendered upon rebuild
},
)
);
}
Card createCard() {
return Card(
child: Row(children: <Widget>[
Text('#p${cards.length + 1}:'),
Column(
children: <Widget>[
createSwitch(cards.length),
createTextField("Description", 70.0),
],),
],),
);
}
#override
void initState() {
super.initState();
cards.add(createCard()); // first card created upon start
}
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: ListView.builder( // List Builder to show all cards
itemCount: cards.length,
itemBuilder: (BuildContext context, int index) {
return cards[index];
},
),
),
RaisedButton(
child: Text('add new'),
onPressed: () => setState(() {
fixedSCs.add(true); // bool element created to create next card
cards.add(createCard());} // create next card
),
),
],
),
),);
}
}
One thing I do not understand in general: Upon rebuild after a state change cards.length} should be my number of cards, let's say 3. And when it renders the 1st card, it passes the line Text("#p${cards.length + 1}"), so it should show #p3 and not #p1. What do I get wrong here?
I meanwhile got this working with quite some logic changes.
I put the switch builder into a stateless widget
class createSwitch extends StatelessWidget {
const createSwitch({
this.label, this.margin=const EdgeInsets.all(0.0), this.width, this.height, this.value, this.onChanged});
final String label; final EdgeInsets margin; final double width; final double height; final bool value;
final Function onChanged;
#override
Widget build(BuildContext context) {
return Container(
child: Row(
children: <Widget>[
Expanded(child: Text(label)),
CupertinoSwitch(
value: value,
onChanged: (bool newValue) {onChanged(newValue);},
),
],
),
),
} }
In the parent stateful controller I created a list to store the switches' state var parameterSCs = [true]; and each time I add a card I add a value whith clicking the button onPressed: () => setState(() {parameterSCs.add(true);}
I no longer store the cards widgets as a list. Instead, I build them directly in the code within a ListView.builder
ListView.builder(
itemCount: parameterSCs.length,
itemBuilder: (BuildContext context, int index) {
return Card( ...
In my real code I have 2 switches per card, so I always add 2 elements and the ListView count is then half of the parameterSCs' length.
I tried loads of approaches, this was the only one working