2
votes

I have written some simplified code where I want the condition of firestore data to be able to update a parent widget.

I get the error: setState() or markNeedsBuild() called during build.

How can I update the state of myVar from inside StreamBuilder? After updating myVar the StreamBuilder will not be accessed, instead only the text Waiting... should be displayed.

Edit: I updated myVar to be used as the document ID of Firestore. To clarify, I want to be able to update the document of the StreamBuilder from inside the StreamBuilder. In other words as the question says, update stateful widget from child StreamBuilder.

class MyWidget extends StatefulWidget {
  MyWidget({Key key})
      : super(key: key);

  @override
  _MyWidgetState createState() {
    return _MyWidgetState();
  }
}

class _MyWidgetState extends State<MyWidget> {
  String myVar = "someID";

  @override
  Widget build(BuildContext context) {
    if (myVar == null) {
      // update myVar async, so that StreamBuilder will be re-rendered
      asyncFunctUpdate(myVar); // edit: finally got it to work if doing setState from asyncFuncUpdate(myvar).then(()=>setState)
      return Text("Waiting...");
    }
    else
      return StreamBuilder<DocumentSnapshot>(
          stream: Firestore.instance
              .collection('myCollection')
              .document(myVar)
              .snapshots(),
          builder: (context, orderSnapshot) {
            if (!orderSnapshot.data.exists)
              setState(() {
                myVar = null;
              });
            else return Text(orderSnapshot);
          });
  }
}

Workaround: In my case it was simpler to just return buttons from the StreamBuilder and setState as normally from onPressed functions.

Solution: Instead of doing setState from an async function do it within the .then clause (see code above)

2
You should create an async method and call it from stream builder. Something like: void setMyVar(String var) async { setState(() { myVar = var}). This should run after the build is done. - danypata
@danypata Calling a function that uses the async keyword even if it doesn't need it will not change anything about the way that function is executed - it will still be executed synchronously, before build completes. As for the actual solution, check Artem's answer. - Ovidiu
It's true that making the call async doesn't work. It returns the same error. I updated the code because it didn't portray my question clearly enough. - Robin Manoli

2 Answers

0
votes

In general, you do not have to do what you are doing now. You can do return Text ("Waiting ..."); right away without calling setState. This is a better decision in your case. You respond to events coming from the stream and must return content according to the current state

return StreamBuilder<DocumentSnapshot>(
      stream: Firestore.instance
          .collection('myCollection')
          .document('myDocument')
          .snapshots(),
      builder: (context, orderSnapshot) {
        if (!orderSnapshot.data.exists)
            return Text("Waiting...");
        else return Text(orderSnapshot);
      });
0
votes

You can make the update async like putting it inside an anonymous async callback like

() async {
    setState();
}();

OR

You can avoid having state in that particular use case like:

@override
  Widget build(BuildContext context) {
      return StreamBuilder<DocumentSnapshot>(
        stream: Firestore.instance
          .collection('myCollection')
          .document('myDocument')
          .snapshots(),
        builder: (context, orderSnapshot) {
          if (!orderSnapshot.data.exists) {
            return Text("Waiting...");
          } else {
            return Text(orderSnapshot);
          }
        });
  }