setState() or markNeedsBuild() called during build on ListView

Debanjan Chakraborty picture Debanjan Chakraborty · Aug 6, 2019 · Viewed 10.6k times · Source

So I am trying to refactor my listView logic. Basically my ListView has become cumbersome with the UI logic , so I decided, why not move certain parts of the UI logic to another class

This is my code ListPage.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:sample_flutter_works/ListTextArea.dart';
import 'package:sample_flutter_works/Model.dart';
import 'dart:convert';
import 'package:sample_flutter_works/RefreshTableContainer.dart';

class ListPage extends StatefulWidget {
  @override
  MyListPage createState() => MyListPage();
}

class MyListPage extends State<ListPage> {
  MessageList messageList;
  List<int> viewTimeInfo;
  ScrollController _controller;

  _scrollListener() {

  }

  @override
  void initState() {
     super.initState();
    SystemChrome.setPreferredOrientations(
        [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);

    _controller = ScrollController();
    _controller.addListener(_scrollListener);

    loadMessages(
        completionBlock: (dataSet) => {
              setState(() {
                messageList = dataSet;
              })
            });
  }

  void loadMessages({completionBlock}) async {
    var jsonString = await rootBundle.loadString('assets/Chat.json');
    final jsonResponse = json.decode(jsonString);
    if (jsonResponse != null) {
      completionBlock(MessageList.fromJSON(jsonResponse));
    } else {
      completionBlock(null);
    }
  }

  Widget listLayout() {
    return ListView.separated(
        padding: const EdgeInsets.all(8.0),
        itemCount: (messageList != null && messageList.msgList != null)
            ? messageList.msgList.length
            : 0,
        separatorBuilder: (context, index) => Divider(
              color: Colors.black,
              height: 4.0,
            ),
        itemBuilder: (BuildContext context, int index) {
          var msgValToSend =
              (messageList != null && messageList.msgList != null)
                  ? messageList.msgList[index]
                  : null;

          return Stack(
            children: <Widget>[
              IntrinsicHeight(
                child: Row(
                  children: <Widget>[
                    getTheImageLayout(msgValToSend),
                    new ListTextArea(
                        msg: msgValToSend,
                        didTapOnTextArea: tappedOnTextArea,
                        visibilityCheck: checkForVisibility)
                  ],
                ),
              )
            ],
          );
        });
  }


  tappedOnTextArea(Message msg) {
    var viewedInfo = this.viewTimeInfo;
    if (viewedInfo != null) {
      var indexOfTappedElement = viewedInfo.indexOf(msg.messageID);

      if (indexOfTappedElement != null && indexOfTappedElement != -1) {
        viewedInfo.removeAt(indexOfTappedElement);
      } else {
        viewedInfo.add(msg.messageID);
      }
    } else {
      viewedInfo = [msg.messageID];
    }

    setState(() {
              viewTimeInfo = viewedInfo;
            });
  }

  checkForVisibility(bool _visible, Message msg) {
    if (msg != null && this.viewTimeInfo != null) {
      var checkForIndex = this.viewTimeInfo.indexOf(msg.messageID);
      if (checkForIndex != null && checkForIndex != -1) {
        _visible = true;
      }
    }
  }


  Widget getTheImageLayout(Message msg) {
    return Expanded(
        flex: 2,
        child: Align(
            alignment: Alignment.topLeft,
            child: Padding(
              padding: EdgeInsets.fromLTRB(5, 2.5, 0, 0),
              child: Container(
                  color: Colors.red,
                  height: 50,
                  child: Row(
                    children: <Widget>[
                      userImageView(msg),
                    ],
                  )),
            )));
  }

  Widget userImageView(Message msg) {
    return Expanded(
        flex: 8,
        child: Align(
            alignment: Alignment.centerLeft,
            child: Container(
                width: 40.0,
                height: 40.0,
                decoration:
                    BoxDecoration(shape: BoxShape.circle, color: Colors.green),
                child: ClipOval(
                  child: Image.network(
                    (msg.msgUser.userPicUrl != null)
                        ? msg.msgUser.userPicUrl
                        : 'https://picsum.photos/250?image=9',
                    fit: BoxFit.fill,
                  ),
                ))));
  }

  Future<void> refreshTheChatTable() async {
    print(" This is where the logic of pulll 2 refresh must be written ");

    loadMessages(
        completionBlock: (dataSet) => {
              setState(() {
                messageList = dataSet;
              })
            });
  }

  @override
  Widget build(BuildContext context) {
    return new RefreshTableContainer(
      listLayout: listLayout(),
      pull2RefreshAction: refreshTheChatTable,
    );
  }
}

ListTextArea.dart

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:sample_flutter_works/Model.dart';

class ListTextArea extends StatelessWidget {

  Message msg;
  Function didTapOnTextArea;
  Function visibilityCheck;

  ListTextArea({
    this.msg,
    this.didTapOnTextArea,
    this.visibilityCheck
  });

  @override
  Widget build(BuildContext context) {
    return Expanded(
        flex: 8,
        child: GestureDetector(
          onTap: didTapOnTextArea(msg),
          child: Padding(
            padding: EdgeInsets.fromLTRB(0, 2.5, 10, 0),
            child: Column(
              children: getChildWidgetArray(msg) ,
            ),
          ),
        ));
  }

  List<Widget> getChildWidgetArray(Message msg) {

    var elementalArray = [
      Align(
        alignment: Alignment.topLeft,
        child: Text(
          (msg != null) ? msg.msgContent.content : "Data Loading",
          style: TextStyle(
            background: Paint()..color = Colors.orange,
          ),
        ),
      ),
      Spacer(), // Defaults to a flex of one.
      Align(
        alignment: Alignment.bottomRight,
        child: Text(
          'Date of sending',
          textDirection: TextDirection.rtl,
          style: TextStyle(
            background: Paint()..color = Colors.blue,
          ),
        ),
      )
    ];

    var _visible = false;
    visibilityCheck(_visible,msg);

    var timeInfo = AnimatedOpacity (
          opacity: _visible ? 1.0 : 0.0,
          duration: Duration(milliseconds: 500),
          child: Align(
            child: _visible ? (Align(alignment: Alignment.topLeft,child:Column(children: <Widget>[Text("Last Read :" + (msg.msgTimeInfo.lastReadInfo)),
                                      Text("Delievered :" + (msg.msgTimeInfo.deliveredInfo))],))): null));
        elementalArray.add(timeInfo);

    return elementalArray;
  }
}

The error is as follows: enter image description here

What I am trying to do ( or had done earlier on when the entire code was in ListPage.dart ) was dynamically calculated cells in a listView, each cell responding to a tap action that shows in more data. I don't understand what I did wrong here at all.

I called the setState in init but inside a callback function. The statelesswidget ListTextArea will not handle the state at all, but returns the tapAction to the StateFulWidget ListPage.dart.

So why am I getting this error. Any insights would be helpful.

Answer

alfiepoleon picture alfiepoleon · Aug 27, 2020

In my case, the error occurred when I was setting the state before build was complete, so, I deferred it to the next tick and it worked.

previously

myFunction()

New

Future.delayed(Duration.zero, () async {
  myFunction();
});