motasimfuad
motasimfuad

Reputation: 678

Removing item from ListView.Builder always removes the last item in Flutter

When I tried to remove an item, every time the last item get removed. After searching for solutions, I found out it works this way with stateful widgets, and the solution is to add keys in the widgets.

So I added keys but the problem isn't gone. The last item still gets removed. Bellow, I tried to show the situation as detailed as possible. At the very last, you will see, when I remove the item at index(0) it gets called but index(1) gets disposed from the UI. But in the list, the 1st item got removed properly.

This is the ListView.builder

                     ListView.builder(
                        primary: false,
                        shrinkWrap: true,
                        scrollDirection: Axis.vertical,
                        physics: const NeverScrollableScrollPhysics(),
                        itemCount: saleItems.length,
                        itemBuilder: (BuildContext context, int index) {
                          print('Value key: ${ValueKey(index)}');
                          return ProductItemWidget(
                            key: ValueKey(index),
                            itemContext: context,
                            mainItems: batches,
                            onDelete: () {
                              setState(() {
                                saleItems.remove(saleItems[index]);
                                print(
                                    'deleted $index - value ${ValueKey(index)}');
                                print(saleItems);
                              });
                            },
                            onNewSaleItem: (newItem) {
                              setState(() {
                                saleItems[index] = newItem;
                              });
                              print(saleItems);
                            },
                          );
                        },
                      ),

Adding new items to the list

SizedBox(
                        key: _addButtonKey,
                        child: KFilledButton(
                          text: 'New Sale Item',
                          onPressed: () {
                            setState(() {
                              saleItems.add(newSaleItemModal);
                            });
                            scrollToAddButton();
                          },
                        ),
                      ),

Instance of the item and the list

  NewSaleItemModal newSaleItemModal = NewSaleItemModal();
  List<NewSaleItemModal> saleItems = [];

ProductItemWidget() page

This is the Constructor

class ProductItemWidget extends StatefulWidget {
  void Function() onDelete;
  List<dynamic>? mainItems;
  BuildContext? itemContext;
  Function(NewSaleItemModal newSaleItem) onNewSaleItem;
  ProductItemWidget({
    Key? key,
    required this.onDelete,
    required this.onNewSaleItem,
    this.mainItems,
    this.itemContext,
  }) : super(key: key);

  @override
  State<ProductItemWidget> createState() => _ProductItemWidgetState();
}

These are the states

  @override
  void initState() {
    super.initState();
    print('Created with key: ${widget.key}');
  }

  @override
  void didChangeDependencies() {
    types = profileRepository.getConfigEnums(type: EnumType.discountType);
    getAllProductNames();
    super.didChangeDependencies();
  }

  @override
  void dispose() {
    super.dispose();
    print('Disposed key: ${widget.key}');
    selectedProductName = null;
    selectedProduct = null;
    selectedType = null;
    _discountController.dispose();
    _rateController.dispose();
    _quantityController.dispose();
    _unitController.dispose();
    _amountController.dispose();
  }

This is the where I added the key

 @override
  Widget build(BuildContext itemContext) {
    return Form(
      key: widget.key,
      child: ..........
      ),
     }

After adding first item

After adding first item, in console...

I/flutter (17380): Value key: [<0>]
I/flutter (17380): Created with key: [<0>]
I/flutter (17380): [NewSaleItemModal(productId: 23, batchId: 88, rate: 35567, quantity: 1, unitId: 1, discount: 0, discountType: null, amount: 35567)]

After adding second item

After adding second item, in console...

I/flutter (17380): Value key: [<1>]
I/flutter (17380): Created with key: [<1>]
I/flutter (17380): [NewSaleItemModal(productId: 23, batchId: 88, rate: 35567, quantity: 1, unitId: 1, discount: 0, discountType: null, amount: 35567), NewSaleItemModal(productId: 4, batchId: 69, rate: 1158, quantity: 1, unitId: 1, discount: 0, discountType: null, amount: 1158)]

Here is the video deleting the first item

When i delete the first item, in console...

I/flutter (17380): deleted 0 - value [<0>]
I/flutter (17380): [NewSaleItemModal(productId: 4, batchId: 69, rate: 1158, quantity: 1, unitId: 1, discount: 0, discountType: null, amount: 1158)]

I/flutter (17380): Value key: [<0>]
I/flutter (17380): Disposed key: [<1>]

Upvotes: 2

Views: 2328

Answers (3)

Mitrakov Artem
Mitrakov Artem

Reputation: 1533

I also had the same problem in two my different projects, and sometimes even applied a "workaround" solution, like Visibility(visible: !_isDeleted, child: MyListViewItem(...) but I always ended up with the same approach:

Use state management libraries. E.g. ScopedModel, Redux or Bloc. This appeared to be the only correct way for me.

In a few words:

  • Create a "model" that is always a "source of truth"
  • All your listView items are just "lightweight" widgets that "reflect" the model (no matter stateful or stateless they are)
  • Once you want to update => update only the model, and all listView items will draw whatever the model wants
  • Want to delete? Just define and call model.removeItemAt(i). All listView items will be rebuilt properly
  • There will be some minor "issues" with updating widgets with user input, like text fields, numeric boxes, etc. This should not stop you. Define TextEditingControllers for all text fields and update the model each time user changes the text

I hope it will help.

Upvotes: 0

Khaled
Khaled

Reputation: 2282

Just use UniqueKey() instead of ValueKey(index) It seems to have solved the problem for me.

Upvotes: 0

Arbiter Chil
Arbiter Chil

Reputation: 1285

hmm try using removeWhere to identify which item to be remove

saleItems.removeWhere((e)=> e.productId == saleItems[index].productId);

Upvotes: 0

Related Questions