John
John

Reputation: 353

flutter setstate rebuild only one child

in flutter I need that when I call setstate, it only rebuilds a widget

I put 2 children in a stack, I need that when a button is pressed, only the second one is rebuilt.

bool popup = false;

Scaffold(
  appBar: AppBar(
    title: const Text('TEST'),
    actions: <Widget>[
      IconButton(                       // + BUTTON
        icon: Icon(Icons.add),
        onPressed: () {
          setState(() {
            popup = true;
          });
        },
      ),
      IconButton(                       // - BUTTON
        icon: Icon(Icons.remove),
        onPressed: () {
          setState(() {
            popup = false;
          });
      ),
    ],
  ),
  body: SafeArea(
    child: Stack(
      children: <Widget>[

        Container(                                        // FIRST WIDGET
          key: ValueKey(1),
          child: Text("Random - "+new Random().nextInt(20).toString())
        ),

        popup ? Center(child: Text("abc")) : Text("") ,   // SECOND WIDGET

      ],

    ),
  ),
);

I expect that when I press the "+" button only the second widget will be re-built, but now it will rebuild all the contents of the stack.

thank you all.

Upvotes: 4

Views: 11386

Answers (5)

Nick Khotenko
Nick Khotenko

Reputation: 767

Another option is to use ValueListenableBuilder:

class _MyHomePageState extends State<MyHomePage> {
  final ValueNotifier<bool> popup = ValueNotifier<bool>(false);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('TEST'),
        actions: <Widget>[
          IconButton(
              // + BUTTON
              icon: Icon(Icons.add),
              onPressed: () {
                popup.value = true;
              }),
          IconButton(
              // - BUTTON
              icon: Icon(Icons.remove),
              onPressed: () {
                popup.value = false;
              })
        ],
      ),
      body: Center(
        child: ValueListenableBuilder<bool>(
            valueListenable: popup,
            builder: (context, value, _) {
              return Stack(
                children: [
                  Text("Random - " + new Random().nextInt(20).toString()),
                  popup.value ? Center(child: Text("abc")) : Text(""),
                ],
              );
            }),
      ),
    );
  }
}

Upvotes: 6

Richardd
Richardd

Reputation: 1012

From the official docs we can read:

"When setState() is called on a State, all descendent widgets rebuild. Therefore, localize the setState() call to the part of the subtree whose UI actually needs to change. Avoid calling setState() high up in the tree if the change is contained to a small part of the tree."

My suggestion, and I use it most of the times, is separate the widget that you want to rebuild in a new StatefulWidget. This way the setState only will be rebuild that widget.

    class MyAppBar extends StatefulWidget
    ...
    class _MyAppBarState extends State<MyAppBar> {
    bool popup = false;

     @override
      Widget build(BuildContext context) {
        return AppBar(
           title: const Text('TEST'),
           actions: <Widget>[
           IconButton(                       // + BUTTON
             icon: Icon(Icons.add),
             onPressed: () {
               setState(() {
               popup = true;
             });
            },
           ),
          IconButton(                       // - BUTTON
            icon: Icon(Icons.remove),
            onPressed: () {
              setState(() {
              popup = false;
            });
          ),
        ],
       ), 
      }

Then call it in your Scaffold:

Scaffold(
  appBar: MyAppBar(), 

Other method I can suggest is using ValueNotifier or notifyListeners(). Please read this page Avoid rebuilding all the widgets repetitively. It is well explained.

Upvotes: 7

Igor Kharakhordin
Igor Kharakhordin

Reputation: 9873

You can use StreamBuilder:

  StreamController<bool> popup = StreamController<bool>();

  @override
  void dispose() {
    popup.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('TEST'),
        actions: <Widget>[
          IconButton(                       // + BUTTON
            icon: Icon(Icons.add),
            onPressed: () => popup.add(true),
          ),
          IconButton(                       // - BUTTON
            icon: Icon(Icons.remove),
            onPressed: () => popup.add(false),
          ),
        ],
      ),
      body: SafeArea(
        child: Stack(
          children: <Widget>[
            Container(                                        // FIRST WIDGET
              key: ValueKey(1),
              child: Text("Random - "+new Random().nextInt(20).toString())
            ),
            StreamBuilder<bool>(
              stream: popup.stream,
              initialData: false,
              builder: (cxt, snapshot) {
                return snapshot.data ? Center(child: Text("abc")) : Text("");
              },
            )
          ],

        ),
      ),
    );
  }

Upvotes: 2

Sunny
Sunny

Reputation: 3265

Here is the example from where you can learn how to build an Inherited model widget to update only specific widgets rather than the whole widgets.

https://medium.com/flutter-community/flutter-state-management-setstate-fn-is-the-easiest-and-the-most-powerful-44703c97f035

Upvotes: 0

Amir Ali Aghamali
Amir Ali Aghamali

Reputation: 642

Remove the setState from the widget you don't want to be changed. And only use setState for the ones you need to rebuild

Or you can consider using inheritedModel widget

Upvotes: 0

Related Questions