First I will show a little how the widgets involved in the problem are structured and then I will explain the problem itself
Widget Structure
I have the following widget CaliberTextFormFields
that displays fields of a form in order to edit objects of the class Caliber
. These objects Caliber
are stored in a list in the property of an object of the class Product
in the provider ProductProvider
. The Widget CaliberTextFormFields
is called in aColumn
In short it would be Caliber
>> Product
>>ProductProvider
And those Caliber
are edited in CaliberTextFormFields
which is called in aColumn
class CaliberTextFormFields extends StatelessWidget {
const CaliberTextFormFields({
required this.caliber,
});
final Caliber caliber;
@override
Widget build(BuildContext context) {
ProductProvider productProvider = Provider.of<ProductProvider>(context); //Provider donde se almacenan los objetos Caliber
return Column(
children: [
IconButton(
icon: const Icon(Icons.delete),
onPressed: ()=>productProvider.removeCaliberFromProductForm(caliber), //Borra el objeto Caliber de la lis[![introducir la descripción de la imagen aquí][1]][1]
),
const Text("Calibre"),
TextFormField(
initialValue: caliber.weight,
onChanged: (value){
caliber.weight = value;
},
),
const Text("Cantidad"),
TextFormField(
initialValue: caliber.amount.toString(),
onChanged: (value){
try{
caliber.amount = int.parse(value);
} catch(e){
print(e);
}
},
),
const Text("Precio"),
TextFormField(
initialValue: caliber.price.toString(),
onChanged: (value){
try{
caliber.price = double.parse(value);
}catch (e){
print(e);
}
},
),
const Text("Peso por caja"),
TextFormField(
initialValue: caliber.weightForBox.toString(),
onChanged: (value){
try{
caliber.weightForBox = double.parse(value);
} catch(e){
print(e);
}
},
),
],
);
}
}
This is what the CalibertTextFormFields widget would look like ( with the graphic styles I've omitted )
The class Caliber
, whose objects are edited with the above widget:
class Caliber {
Caliber({
required this.weight,
required this.price,
required this.amount,
required this.weightForBox,
required this.id,
});
int id;
String weight;
double price;
int amount;
double weightForBox;
}
Here I call the CalibertTextFormFields
:
class ProductForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
ProductProvider productProvider = Provider.of<ProductProvider>(context);
return Form(
key: _formKey,
child: Column(
children: [
//...
..[for (var caliber in productProvider.productForm!.calibers) CaliberTextFormFields(caliber: caliber)],
]
}
And the object Product
, where the objects are stored Caliber
in the property calibers
. This object Product
is stored in the ProductProvider
:
class Product {
Product({required this.id, required this.name, required this.calibers, required this.media});
String name;
String id;
List<Caliber> calibers; //Aquí se almacenan los objetos de la clase Caliber
List<String> media;
Uint8List? coverImage;
}
Finally, this is the Provider where the Product
one containing the Caliber
:
class ProductProvider extends ChangeNotifier{
Product? productForm;
void addCaliberToProductForm(){ //Agrega un Caliber con ID único a la lista
if(productForm!.calibers.isNotEmpty) {
productForm!.calibers.add(Caliber(
id:productForm!.calibers.last.id+1,
weightForBox:0,
weight:"Calibre",
price:0,
amount:0
));
} else {
productForm!.calibers.add(Caliber(
id:0,
weightForBox:0,
weight:"Calibre",
price:0,
amount:0
));
}
notifyListeners();
}
void removeCaliberFromProductForm(Caliber caliber){
productForm!.calibers.removeWhere((element) => element.id == caliber.id);
notifyListeners();
}
}
Problem
The problem is that when there are more than two Caliber
in the list and the top one is deleted with the IconButton
delete one, it is deleted correctly with the function removeCaliberFromProductForm
. But, when the Widget is redrawed with the Caliber
rest, the CaliberTextFormFields
deletion is maintained while the only one that is deleted is the last one CaliberTextFormFields
in the list. In the following way:
However, using the debugger I see that the appropriate item is deleted from the Product
del list. ProductProvider
In addition, when calling CaliberTextFormFields
when redrawing, the Caliber
appropriate parameter is also passed, so I don't know why they redraw incorrectly.
Also, if I add to the beginning of the CaliberTextFormFields
one Text
with the values of the parameter Caliber
that are passed to it, they show the correct data. So I guess the problem must be from the TextFormField
.
Added lines:
Text(caliber.id.toString()),
Text(caliber.weight),
Converting CaliberTextFormFields
to a Statefulidget
and adding the following initState and dispose:
@override
void initState(){
print("initState: ${widget.caliber.id.toString()}");
print("initState: ${widget.caliber.weight}");
super.initState();
}
@override
void dispose(){
print("dispose: ${widget.caliber.id.toString()}");
print("dispose: ${widget.caliber.weight}");
super.dispose();
}
When I perform the above procedure, I get the following:
I/flutter (13925): dispose: 1
I/flutter (13925): dispose: Must not be cleared
Possible reason for the error
I found by searching the documentation for the TextFormField the following could be the reason for the error:
When the widget has focus, it will prevent itself from disposing via its underlying EditableText's AutomaticKeepAliveClientMixin.wantKeepAlive in order to avoid losing the selection. Removing the focus will allow it to be disposed.
However, I don't know how to change that property.
I think I can understand the error, I'll explain it to you:
In that part, you are initializing the
TextFormField
with a specific value, as the widget is not recreated, only the value is updatedcaliber.weight
, but this no longer affects theinitialValue
since that is only created once.What you could do is change it to
StatefulWidget
, remove this line:initialValue: caliber.weight,
and use aTextEditingController
, then when the widget is updated, change the value of the textEditingController.So:
Let me know if it works for you :)