Home > Software engineering >  Calling setState from an instance in a ListView
Calling setState from an instance in a ListView

Time:11-18

I am trying to remove a card widget by tapping on an icon. It has multiple instances in a ListView.builder.

I've been trying to use _callback() passed down to the IconButton but I'm getting a red screen saying setState() is called during build.

import 'package:flutter/material.dart';

//ItemData used in addnew.dart
class ItemData {
  final String id;
  final String score;
  final String title;
  final String description;

  ItemData({required this.id, required this.score, required this.title, required this.description});

}

//Dummy list of items
final itemList = [
  ItemData(id: 'one', score: '30', title: 'Title One', description: 'mock description'),
  ItemData(id: 'two', score: '10', title: 'Title Two', description: 'mock description'),
  ItemData(id: 'three', score: '20', title: 'Title Three', description: 'mock description'),
];

////////////////////


class ListRe extends StatefulWidget {
  const ListRe({Key? key}) : super(key: key);

  @override
  State<ListRe> createState() => _ListReState();
}


class _ListReState extends State<ListRe> {

  _callback(){
    print('delete tapped');
 //  setState(() {    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
// By using a listView we lazily populate the items and pass to the `ListCard` the single item it needs
      body: ListView.builder(
          scrollDirection: Axis.horizontal,
          itemCount: itemList.length,
          itemBuilder: (context, index) {
            return ListCard(item: itemList[index], delete: _callback());
          }),

    );
  }

  @override
  void initState() {
    super.initState();
    sortList(); //perform computations off the build method of the widgets move it to the lifecycle methods of StatefulWidgets or to a State management solution.
  }
}



sortList() {
  itemList.sort((item1, item2) => item2.score.compareTo(item1.score));
}

class ListCard extends StatelessWidget {
  final ItemData item;
  final delete;
  const ListCard({Key? key, required this.item, required this.delete}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: [
              Padding(
                padding:  const EdgeInsets.fromLTRB(0,0,0,0),
                child: Text(item.title,
                    style: TextStyle(fontWeight: FontWeight.bold)
                ),
              ),
              Text('Score: '   item.score),
            ],
          ),
          Column(
            children: [
              IconButton(
                  onPressed: (){
                    print('delete pressed');
                    itemList.remove(item);
                    delete;
                  },
                  icon: Icon(Icons.delete)),
            ],
          )
        ],
      ),

    );
  }
}

Even when I comment out setState() I can't seem to print anything to the console so I'm obviously doing something wrong.

CodePudding user response:

There are several problems in your code, I'll address them one by one.

ListCard(item: itemList[index], delete: _callback())

In doing so you are calling the function _callback() instead of passing it as an argument.

In ListCard, declaring the attribute final delete; without specifying its type might be a problem.

             onPressed: (){
                print('delete pressed');
                itemList.remove(item);
                delete;
              },

Here you aren't actually calling the callback function.

To make it work, firs of all you have to declare the field as final VoidCallback delete;, then creating an instance with ListCard(item: itemList[index], delete: _callback) and using the callback with

              onPressed: (){
                print('delete pressed');
                itemList.remove(item);
                delete();
              },

Besides that I'd like to point out how you're not using the state correctly. You should declare your item list inside your widget state and update it inside a setState call.

Here is how I would write it:

import 'package:flutter/material.dart';

//ItemData used in addnew.dart
class ItemData {
  final String id;
  final String score;
  final String title;
  final String description;

  ItemData({required this.id, required this.score, required this.title, required this.description});

}

////////////////////


class ListRe extends StatefulWidget {
  const ListRe({Key? key}) : super(key: key);

  @override
  State<ListRe> createState() => _ListReState();
}


class _ListReState extends State<ListRe> {
  // itemList is handled by the state
  late List<ItemList> itemList;

  // moved inside the state as a private method
  _sortList() {
    itemList.sort((item1, item2) => item2.score.compareTo(item1.score));
  }

  // here I update the list inside a setState call
  _callback(ItemData item){
    print('delete tapped');
    setState(() {
      itemList.remove(item);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
// By using a listView we lazily populate the items and pass to the `ListCard` the single item it needs
      body: ListView.builder(
          scrollDirection: Axis.horizontal,
          itemCount: itemList.length,
          itemBuilder: (context, index) {
            // callback passed as argument without calling it
            return ListCard(item: itemList[index], delete: _callback);
          }),

    );
  }

  @override
  void initState() {
    super.initState();
    //Dummy list of items
    itemList = [
      ItemData(id: 'one', score: '30', title: 'Title One', description: 'mock description'),
      ItemData(id: 'two', score: '10', title: 'Title Two', description: 'mock description'),
      ItemData(id: 'three', score: '20', title: 'Title Three', description: 'mock description'),
    ];
    _sortList(); //perform computations off the build method of the widgets move it to the lifecycle methods of StatefulWidgets or to a State management solution.
  }
}

class ListCard extends StatelessWidget {
  final ItemData item;
  final VoidCallback delete;
  const ListCard({Key? key, required this.item, required this.delete}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: [
              Padding(
                padding:  const EdgeInsets.fromLTRB(0,0,0,0),
                child: Text(item.title,
                    style: TextStyle(fontWeight: FontWeight.bold)
                ),
              ),
              Text('Score: '   item.score),
            ],
          ),
          Column(
            children: [
              IconButton(
                  onPressed: () => delete(item),
                  icon: Icon(Icons.delete)),
            ],
          )
        ],
      ),
    );
  }
}
  • Related