Home > Back-end >  Flutter nested widgets setState does not work as intended
Flutter nested widgets setState does not work as intended

Time:09-24

I have the following structure:

  • MainBlock. main statefull widget. contains two widgets BlockA and BlockB.
  • BlockA. contains a text and a button.
  • BlockB. contains another widget, BlockBCard, which will be used several times (two times in this example).

What works as intended? When I click on the button in BlockA, the content of the text field in BlockA and BlockBCard changers as desired.

Now to my problem:

In BlockB. In order to use setState, I changed BlockB to a StatefulWidget. Clicking on the button in the BlockBCard, changes the text field in both BlockBCard’s as desired. But the content of the text field in BlockA does not change.

how can I implement the following: Click on the button in one of the BlockBCard’s, both the text field in BlockA and the two text fields in BlockBCard change?

Click on the button in one of the BlockBCard’s, the text field in BlockA changes and the text field in the BlockBCard changes but the text field in the second BlockBCard does not change.

enter image description here

Sample Code:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());
int testCounter = 0;

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MainBlock(),
    );
  }
}
// ------------------------------------ Stateless Widget <<<

class MainBlock extends StatefulWidget {
  @override
  _MainBlockState createState() => _MainBlockState();
}

class _MainBlockState extends State<MainBlock> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        margin: EdgeInsets.all(30.0),
        color: Color(0xFF122C39),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
              child: BlockA(
                counter: testCounter,
                button: () {
                  setState(() {
                    testCounter  ;
                  });
                },
              ),
            ),
            Expanded(
              child: BlockB(),
            ),
          ],
        ),
      ),
    );
  }
}

class BlockA extends StatelessWidget {
  final int counter;
  final Function button;

  BlockA({this.counter, this.button});

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button,
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class BlockB extends StatefulWidget {
  @override
  _BlockBState createState() => _BlockBState();
}

class _BlockBState extends State<BlockB> {
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Expanded(
            child: BlockBCard(
              counter: testCounter,
              button: () {
                setState(() {
                  testCounter  ;
                });
              },
            ),
          ),
          Expanded(
            child: BlockBCard(
              counter: testCounter,
            ),
          ),
        ],
      ),
    );
  }
}

class BlockBCard extends StatelessWidget {
  final int counter;
  final Function button;

  BlockBCard({this.counter, this.button});

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(30.0),
      color: Color(0xFF4C93C7),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button,
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

CodePudding user response:

You got the issue because when you change the value of blockB it could not rebuild ta blockA but it change the global value if you will want to change the blockA's from blockB you should pass as like blockA.


import 'package:flutter/material.dart';

void main() => runApp(MyApp());
int testCounter = 0;

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MainBlock(),
    );
  }
}
// ------------------------------------ Stateless Widget <<<

class MainBlock extends StatefulWidget {
  @override
  _MainBlockState createState() => _MainBlockState();
}

class _MainBlockState extends State<MainBlock> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        margin: EdgeInsets.all(30.0),
        color: Color(0xFF122C39),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
              child: BlockA(
                counter: testCounter,
                button: () {
                  setState(() {
                    testCounter  ;
                  });
                },
              ),
            ),
            Expanded(
              child: BlockB(
                  counter: testCounter,
                  button: () {
                    setState(() {
                      testCounter  ;
                    });
                  }),
            ),
          ],
        ),
      ),
    );
  }
}

class BlockA extends StatelessWidget {
  final int counter;
  final Function button;

  BlockA({this.counter, this.button});

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button,
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class BlockB extends StatefulWidget {
  final int counter;
  final Function button;

  BlockB({this.counter, this.button});

  @override
  _BlockBState createState() => _BlockBState();
}

class _BlockBState extends State<BlockB> {
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Expanded(
            child: BlockBCard(
              counter: testCounter,
              button:widget.button,
            ),
          ),
          Expanded(
            child: BlockBCard(
              counter: testCounter,
            ),
          ),
        ],
      ),
    );
  }
}

class BlockBCard extends StatelessWidget {
  final int counter;
  final Function button;

  BlockBCard({this.counter, this.button});

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(30.0),
      color: Color(0xFF4C93C7),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button,
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

CodePudding user response:

As @Jahidul Islam explained, you have to pass your data from MainBlock to BlocB as you do from MainBlock to BlocA.

In general in Flutter, you want to avoid using mutable global variable precisely for that reason. When a variable update, flutter does not know about it. If you call setState the widget tree bellow will be rebuild. This is why you will often hear "Lift the state up" because the state has to be contained in a high enough widget so that it can be passed down through the widget tree to every widget that needs it. In your case, since BlocA and BlockBCard need the counter, it has to be created inside there lower common ancestor: MainBlock.

Here is the concrete implementation but the most important thing is to have understood the reasoning explained above:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MainBlock(),
    );
  }
}

class MainBlock extends StatefulWidget {
  @override
  _MainBlockState createState() => _MainBlockState();
}

class _MainBlockState extends State<MainBlock> {
  int testCounter = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        margin: EdgeInsets.all(30.0),
        color: Color(0xFF122C39),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
              child: BlockA(
                counter: testCounter,
                button: () {
                  setState(() {
                    testCounter  ;
                  });
                },
              ),
            ),
            Expanded(
              child: BlockB(
                counter: testCounter,
                button: () {
                  setState(() {
                    testCounter  ;
                  });
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class BlockA extends StatelessWidget {
  final int counter;
  final VoidCallback button;

  BlockA({required this.counter, required this.button});

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button,
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class BlockB extends StatelessWidget {
  final int counter;
  final VoidCallback button;

  BlockB({required this.counter, required this.button});

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Expanded(
            child: BlockBCard(
              counter: counter,
              button: button,
            ),
          ),
          Expanded(
            child: BlockBCard(
              counter: counter,
            ),
          ),
        ],
      ),
    );
  }
}

class BlockBCard extends StatelessWidget {
  final int counter;
  final VoidCallback? button;

  BlockBCard({required this.counter, this.button});

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(30.0),
      color: Color(0xFF4C93C7),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Center(
            child: Text(
              counter.toString(),
              style: TextStyle(
                color: Color(0xFFFFFFFF),
                fontSize: 22.0,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: button ?? (() {}),
              child: Text(
                'Button',
                style: TextStyle(
                  color: Color(0xFFFFFFFF),
                  fontSize: 22.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

CodePudding user response:

If we refresh the MainBlock it's children will also get the updated state. We can use callback for it.

On BlocKB


class BlockB extends StatefulWidget {
  final VoidCallback ontap;
  const BlockB({
    Key? key,
    required this.ontap,
  }) : super(key: key);
  @override
  _BlockBState createState() => _BlockBState();
}

class _BlockBState extends State<BlockB> {
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(10.0),
      color: Color(0xFF265672),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Expanded(
            child: BlockBCard(
              counter: testCounter,
              button: () {
                // setState(() { /// we dont need it while parent widget will refress it
                testCounter  ;
                // });

                widget.ontap();
              },
            ),
          ),
          Expanded(
            child: BlockBCard(
              counter: testCounter,
              button: () {
                /// you may also wanted to increment here

                testCounter  ;

                widget.ontap();
              },
            ),
          ),
        ],
      ),
    );
  }
}

On MainBlock


       Expanded(
              child: BlockB(
                ontap: () {
                  setState(() {});
                },
              ),
            ),

  • Related