When the textfield_tag widget is rebuilt for the second time, it produces the A TextEditingController was used after being disposed error. I am displaying the widget based on the toggle switch. I have tried to pass to the widget an TextFieldTagsController() that I instantiate in the initState() method but i get the same error
BEFORE SHOWING THE WIDGET
AFTER SHOWING THE WIDGET
NOW AFTER I TOGGLE THE SWITCH FOR THE SECOND TIME
HERE IS THE CODE
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:marketplace/bloc/bloc.dart';
import 'package:marketplace/models/product.dart';
import 'package:image_picker/image_picker.dart';
import 'package:marketplace/screens/my_store_screen.dart';
import 'dart:io';
import 'package:textfield_tags/textfield_tags.dart';
import '../main.dart';
import '../utils/screen_utils.dart';
class AddProductScreenTwo extends StatefulWidget {
static const routeName = '/add_product_screen_two';
Product? _product;
AddProductScreenTwo(this._product);
@override
_AddProductScreenTwoState createState() =>
_AddProductScreenTwoState(_product);
}
class _AddProductScreenTwoState extends State<AddProductScreenTwo> {
final _formKey = GlobalKey<FormState>();
var bloc = getIt<Bloc>();
late String _title, _description, _model, _sku, _price, _stock;
String currencyDropDownValue = 'USD';
String categoryDropDownValue = 'Shoes';
String subcategoryDropDownValue = 'Sport';
late String _colorDropDownValue, _sizeDropDownValue;
List<String> currencies = [
"USD",
"THA",
];
var categories = ['Shoes', 'Pants', 'Shirt', 'Smoking'];
var _colorOptions = ['Red', 'Green', 'Yellow'];
var _sizeOptions = ['L', 'M', 'S'];
var subcategories = ['Sport', 'Casual'];
List<XFile> _imageFileList = [];
var _image;
var imagePicker;
bool _isUpdating = false;
Product? _productToUpdate;
int _deliveryMethodValue = 1;
bool _hasVariant = false;
// var _txtFieldTagController;
_AddProductScreenTwoState(this._productToUpdate) {
_colorDropDownValue = _colorOptions[0];
_sizeDropDownValue = _sizeOptions[0];
}
@override
void initState() {
print("calling INITSTATE");
// _txtFieldTagController = TextFieldTagsController();
super.initState();
imagePicker = new ImagePicker();
}
@override
Widget build(BuildContext context) {
ScreenUtils().init(context);
return Scaffold(
body: SafeArea(
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.symmetric(
horizontal: getProportionateScreenWidth(16),
),
child: Column(
// mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Add New Product',
style: Theme.of(context).textTheme.headline3!.copyWith(
fontWeight: FontWeight.bold,
),
), // Title : Add New Product
SizedBox(
height: getProportionateScreenHeight(20),
),
TextFormField(
initialValue: _productToUpdate == null
? ""
: _productToUpdate!.name.toString(),
cursorColor: Theme.of(context).cursorColor,
validator: (text) => cannotBeEmptyValidator(text),
onChanged: (value) {
_title = value;
},
decoration: InputDecoration(
labelText: 'Title',
helperText: 'eg. Long Sleeve tshirt',
// errorText: _titleError,
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(0.0)))),
), // Title field
SizedBox(
height: getProportionateScreenHeight(15),
),
TextFormField(
initialValue: _productToUpdate == null
? ""
: _productToUpdate!.brand.toString(),
cursorColor: Theme.of(context).cursorColor,
validator: (text) => cannotBeEmptyValidator(text),
onChanged: (value) {
_description = value;
},
decoration: InputDecoration(
labelText: 'Description',
// errorText: _brandError,
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(0.0)))),
), // Description Field
SizedBox(
height: getProportionateScreenHeight(15),
),
Text("Categories"), // Category selector label
Container(
padding: const EdgeInsets.all(6.0),
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(0.0)),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: categoryDropDownValue,
icon: const Icon(Icons.arrow_drop_down),
iconSize: 32,
elevation: 16,
style: const TextStyle(
fontSize: 20, color: Colors.black54),
onChanged: (String? newValue) {
setState(() {
categoryDropDownValue = newValue ?? "";
});
},
items: categories
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
), // Category selector field
SizedBox(
height: getProportionateScreenHeight(25),
),
Text("Images"), // Image selector label
SizedBox(
height: getProportionateScreenHeight(15),
),
_imageFileList.length != 0
? SizedBox(
height: 200,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
return SizedBox(
height: 200,
width: 200,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(children: [
Image.file(
File(_imageFileList[index].path)),
Positioned(
bottom: 5.0,
right: 5.0,
child: IconButton(
onPressed: () => removeImage(
_imageFileList[index]),
icon: Icon(
Icons.delete_forever,
color: Colors.red,
)),
),
]),
),
);
},
itemCount: _imageFileList.length,
),
)
: Text("no image"),
IconButton(
onPressed: () => pickMultipleImages(),
icon: Icon(Icons.add_a_photo_outlined)),
SizedBox(
height: getProportionateScreenHeight(15),
),
TextFormField(
initialValue: _productToUpdate == null
? ""
: _productToUpdate!.price.toString(),
keyboardType: TextInputType.number,
cursorColor: Theme.of(context).cursorColor,
validator: (text) => cannotBeEmptyValidator(text),
onChanged: (value) {
_price = value;
},
decoration: InputDecoration(
labelText: 'Price',
// errorText: _priceError,
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(0.0)))),
), // Price Field
SizedBox(
height: getProportionateScreenHeight(15),
),
TextFormField(
initialValue: _productToUpdate == null
? ""
: _productToUpdate!.sku.toString(),
cursorColor: Theme.of(context).cursorColor,
validator: (text) => cannotBeEmptyValidator(text),
onChanged: (value) {
_sku = value;
},
decoration: InputDecoration(
labelText: 'SKU',
// errorText: _skuError,
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(0.0)))),
), // SKU Field
SizedBox(
height: getProportionateScreenHeight(15),
),
TextFormField(
keyboardType: TextInputType.number,
cursorColor: Theme.of(context).cursorColor,
validator: (text) => cannotBeEmptyValidator(text),
onChanged: (value) {
_stock = value;
},
decoration: InputDecoration(
labelText: 'Stock Quantity',
// errorText: _stockError,
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(0.0)))),
), // Stock Quantity field
SizedBox(
height: getProportionateScreenHeight(15),
),
Text("Shipping methods"), // Delivery options label
ListTile(
title: Text("Home Delivery"),
leading: Radio<int>(
value: 1,
groupValue: _deliveryMethodValue,
onChanged: (value) {
if (value != null) {
setState(() {
_deliveryMethodValue = value;
});
}
},
)), // Home delivery option
ListTile(
title: Text("Pick up in store"),
leading: Radio<int>(
value: 2,
groupValue: _deliveryMethodValue,
onChanged: (value) {
if (value != null) {
setState(() {
_deliveryMethodValue = value;
});
}
},
)), // Pick up in store option
SizedBox(
height: getProportionateScreenHeight(15),
),
TextFormField(
keyboardType: TextInputType.number,
initialValue: _productToUpdate == null
? ""
: _productToUpdate!.sku.toString(),
cursorColor: Theme.of(context).cursorColor,
validator: (text) => cannotBeEmptyValidator(text),
onChanged: (value) {
_sku = value;
},
decoration: InputDecoration(
labelText: 'Weight in KG',
// errorText: _skuError,
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(0.0)))),
), // Weight Field
SizedBox(
height: getProportionateScreenHeight(15),
),
Text("Variants"), // Variants label
Container(
padding: const EdgeInsets.all(6.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(0.0)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Product has variant",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(
fontWeight: FontWeight.bold,
),
),
Switch(
value: _hasVariant,
onChanged: (val) {
setState(() {
_hasVariant = val;
});
})
]),
), // Variants field
SizedBox(
height: getProportionateScreenHeight(15),
),
_hasVariant
? TextFieldTags(
textSeparators: [
" ", //seperate with space
',' //sepearate with comma as well
],
initialTags: _colorOptions,
onTag: (tag) => _colorOptions.add(tag),
onDelete: (tag) => _colorOptions.remove(tag),
tagsStyler: TagsStyler(
//styling tag style
tagTextStyle:
TextStyle(fontWeight: FontWeight.normal),
tagDecoration: BoxDecoration(
color: Colors.blue[100],
borderRadius: BorderRadius.circular(0.0),
),
tagCancelIcon: Icon(Icons.cancel,
size: 18.0, color: Colors.blue[900]),
tagPadding: EdgeInsets.all(6.0)),
textFieldStyler: TextFieldStyler(
//styling tag text field
textFieldBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue, width: 2))),
// textFieldTagsController: _txtFieldTagController,
)
: Container(),
SizedBox(
height: getProportionateScreenHeight(15),
),
Container(
padding: const EdgeInsets.all(6.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(0.0)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Show to customers",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(
fontWeight: FontWeight.bold,
),
),
Switch(
value: true,
onChanged: (val) {
//TODO update the product visibility value online
// setState(() {
// switchValue = val;
// });
})
]),
), // Availability field
SizedBox(
height: getProportionateScreenHeight(15),
),
StreamBuilder<bool>(
stream: bloc.isPostingProduct,
builder: (context, snapshot) {
print("SNAPSHOT: ${snapshot.data}");
if (snapshot.data == true) {
// Product is being posted
return CircularProgressIndicator();
}
return Container();
}),
SizedBox(
height: getProportionateScreenHeight(15),
),
ElevatedButton(
onPressed: () {
_submit();
},
child: Text('ADD PRODUCT',
style: TextStyle(fontWeight: FontWeight.bold)),
), // Add product button
],
),
)
],
),
),
),
),
);
}
Future pickMultipleImages() async {
try {
var images = await ImagePicker().pickMultiImage();
setState(() {
_imageFileList.addAll(images!);
});
for (var image in _imageFileList) {
print("Multiple images picked : " image.path);
}
} catch (e) {
print("Error : $e");
}
}
void removeImage(XFile imageFile) {
setState(() {
_imageFileList.remove(imageFile);
});
}
void _submit() async {
//TODO
// validate all the form fields
if (_formKey.currentState!.validate()) {
ProductTwo product = ProductTwo(
title: _title,
description: _description,
sku: _sku,
price: double.parse(_price),
);
print("POSTING PRODUCT : $product");
var result = await bloc.postProduct(product);
if (result) {
const snackBar = SnackBar(
content: Text('Your product was saved successfully'),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
Navigator.of(context).pushReplacementNamed(MyStoreScreen.routeName);
}
}
}
_showPostSuccessDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
height: 120,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.done, size: 64, color: Colors.green),
SizedBox(
height: getProportionateScreenHeight(20),
),
Text(
"Your product was successfully saved",
style: TextStyle(color: Colors.green),
),
]),
),
);
},
);
}
String? cannotBeEmptyValidator(String? text) {
if (text == null || text.isEmpty) {
return 'Can\'t be empty';
}
return null;
}
}
CodePudding user response:
Try to add "mounted" to check if the widget is not dispose.
_hasVariant && mounted
? TextFieldTags(
textSeparators: [
" ", //seperate with space
',' //sepearate with comma as well
],
initialTags: _colorOptions,
onTag: (tag) => _colorOptions.add(tag),
onDelete: (tag) => _colorOptions.remove(tag),
tagsStyler: TagsStyler(
//styling tag style
tagTextStyle:
TextStyle(fontWeight: FontWeight.normal),
tagDecoration: BoxDecoration(
color: Colors.blue[100],
borderRadius: BorderRadius.circular(0.0),
),
tagCancelIcon: Icon(Icons.cancel,
size: 18.0, color: Colors.blue[900]),
tagPadding: EdgeInsets.all(6.0)),
textFieldStyler: TextFieldStyler(
//styling tag text field
textFieldBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue, width: 2))),
// textFieldTagsController: _txtFieldTagController,
)
: Container(),
CodePudding user response:
This is a bug in version 1.4.4 of the textfield_tags
library. It currently stores a TextEditingController
and FocusNode
in static fields and reuses them for every instance of TextFieldTags
.
The only thing you can do is file an issue on the tracker and downgrade to 1.4.3 until the problem is fixed in a later version.