Flutter Callback with return error setState() or markNeedsBuild called during build

I use PopupMenuButton to edit the listView. For every action PopupMenuButton (remove item, add item, edit item in listView) trying to do Callback. For example, the simplest case is to delete

//_editorChannels - data with which I fill in the list
delete(dynamic val) {
 setState(() => _editorChannels.removeWhere((data) => data == val));
}

But since I pass the callback function delete to the widget, when I create it I get error setState() or markNeedsBuild called during build. If change and remove the setState(), then no error occurs, but of course the list will not be updated when the item is deleted via callback.

delete(dynamic val) {
  _editorChannels.removeWhere((data) => data == val);
}

My PopupMenuButton Widget

  class PopMenuWidget2 extends StatelessWidget {
  final VoidCallback onDelete;

  const PopMenuWidget2({Key key, this.onDelete}) : super(key: key);

  @override
  Widget build(BuildContext context) => PopupMenuButton<int>(
    onSelected: (result) {
      //On View
      if (result == 0) {}
      //On Edit
      if (result == 1) { }

      //OnDelete Callback run
      if (result == 2) {
        onDelete();
      }
    },
    itemBuilder: (context) => [
      PopupMenuItem(
        value: 0,
        child: Row(
          children: <Widget>[
            Icon(
              Icons.remove_red_eye_rounded,
              color: Colors.black38,
            ),
            Text('  View Option', style: TextStyle(color: Colors.black38)),
          ],
        ),
      ),
      PopupMenuItem(
        value: 1,
        child: Row(
          children: <Widget>[
            Icon(
              Icons.create,
              color: Colors.black38,
            ),
            Text('  Edit Option', style: TextStyle(color: Colors.black38)),
          ],
        ),
      ),
      PopupMenuItem(
        value: 2,
        child: Row(
          children: <Widget>[
            Icon(
              Icons.delete,
              color: Colors.black38,
            ),
            Text('  Delete Option',
                style: TextStyle(color: Colors.black38)),
          ],
        ),
      ),
    ],
  );
 }

Main Widget

  class EditorPage extends StatefulWidget {

  EditorPage({Key key, this.Channels}) : super(key: key);

  final List<Channel> Channels;

  static const String routeName = "/EditorPage";

  @override
  _EditorPageState createState() => new _EditorPageState();
  }

  class _EditorPageState extends State<EditorPage> {
  Stream<Mock> _result;
  final _coreObj = new Core();

 List<Channel> _editorChannels;


  //callback onDelete
  delete(dynamic val) {
  setState(() => _editorChannels.removeWhere((data) => data == val));
 }

 @override
 void initState() {
 _editorChannels = widget.Channels;
 super.initState();
 }


@override
Widget build(BuildContext context) {
return DefaultTabController(
    length: 2,
    child: new Scaffold(
        appBar: AppBar(
          title: Text(''),
          bottom: TabBar(tabs: [
            Tab(icon: FaIcon(FontAwesomeIcons.calendarCheck), text: "One"),
            Tab(icon: FaIcon(FontAwesomeIcons.tasks), text: "Two"),
          ]),
        ),
        body: SafeArea(
            child: TabBarView(children: [
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Expanded(
                child: Scaffold(
                  body: ListView.builder(
                      itemCount: _editorChannels == null
                          ? 0
                          : _editorChannels.length,
                      itemBuilder: (context, index) {
                        final item = _editorChannels[index];
                        return Card(
                          shadowColor: Colors.black26,
                          margin: EdgeInsets.all(3.0),
                          clipBehavior: Clip.antiAlias,
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(2),
                          ),
                          child: ListTile(
                              title: Container(
                                  child: Text(
                                item.Name != null ? item.Name : '',
                                style: new TextStyle(
                                    fontWeight: FontWeight.bold,
                                    fontSize: 16.0),
                              )),
                              subtitle: Text(item.Url),
                              onTap: () => {},
                              isThreeLine: false,
                              leading: getIconbyId(item.Status),
                              
                              

                               //Pass callback   
                              trailing: PopMenuWidget2(
                                onDelete: delete(item),
                              )),


                        );
                      }),
                ),
              )
            ],
          ),
        ]))));
    }
  }

Upvotes: 0

Views: 918

Answers (2)

Spatz
Spatz

Reputation: 20118

Instead of using VoidCallback you have to define custom type:

typedef MyCustomCallback<T> = void Function(T value);

and change onDelete definition to:

      final MyCustomCallback onDelete;

finally fix the callback invocation to:

      if (result == 2) {
        onDelete(value);
      }
      
      // ...

      trailing: PopMenuWidget2(
        onDelete: (item) => delete(item),
      )),

Upvotes: 1

user14596272
user14596272

Reputation:

For setState() to work you need to have PopMenuWidget2 as StatefulWidget . Change

class PopMenuWidget2 extends StatelessWidget {

to

class PopMenuWidget2 extends StatefulWidget {

Also Pass you callback function like link on it, not like result of function execution onDelete: () => onDelete(item)

  trailing: PopMenuWidget(
  onDelete: () => onDelete(item),
  )),

Full Code:

class PopMenuWidget2 extends StatefulWidget {
  final VoidCallback onDelete;

  const PopMenuWidget2({Key key, this.onDelete}) : super(key: key);
  @override
  _PopMenuWidget2State createState() => _PopMenuWidget2State();
}

class _PopMenuWidget2State extends State<PopMenuWidget2> {
  @override
  Widget build(BuildContext context) => PopupMenuButton<int>(
    onSelected: (result) {
      //On View
      if (result == 0) {}
      //On Edit
      if (result == 1) { }

      //OnDelete Callback run
      if (result == 2) {
        onDelete();
      }
    },
    itemBuilder: (context) => [
      PopupMenuItem(
        value: 0,
        child: Row(
          children: <Widget>[
            Icon(
              Icons.remove_red_eye_rounded,
              color: Colors.black38,
            ),
            Text('  View Option', style: TextStyle(color: Colors.black38)),
          ],
        ),
      ),
      PopupMenuItem(
        value: 1,
        child: Row(
          children: <Widget>[
            Icon(
              Icons.create,
              color: Colors.black38,
            ),
            Text('  Edit Option', style: TextStyle(color: Colors.black38)),
          ],
        ),
      ),
      PopupMenuItem(
        value: 2,
        child: Row(
          children: <Widget>[
            Icon(
              Icons.delete,
              color: Colors.black38,
            ),
            Text('  Delete Option',
                style: TextStyle(color: Colors.black38)),
          ],
        ),
      ),
    ],
  );
}

Upvotes: 1

Related Questions