Search code examples
fluttertexteditingcontroller

When the textfield_tag widget is rebuilt for the second time, it produces the A TextEditingController was used after being disposed error


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

enter image description here

AFTER SHOWING THE WIDGET

enter image description here

NOW AFTER I TOGGLE THE SWITCH FOR THE SECOND TIME

enter image description here

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

Solution

  • 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.