Force rebuild of a stateful child widget in flutter

1,919

Solution 1

USE THIS:

class _MyHomePageState extends State<MyHomePage> {
  int count = 5;
  MyListWidget myListWidget = MyListWidget(5);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Row(
      children: [
        Expanded(
          child: MaterialButton(
            child: Text('Click me'),
            color: Colors.red,
            onPressed: () {
              setState(() {
                 count++;
                 myListWidget = MyListWidget(count);
               });
            },
          ),
        ),
        myListWidget,
      ],
    ));
  }
}

Solution 2

I don't think the accepted answer is accurate, Flutter will retain the state of MyListWidget because it is of the same type and in the same position in the widget tree as before.

Instead, force a widget rebuild by changing its key:

class _MyHomePageState extends State<MyHomePage> {
  int count = 5;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          Expanded(
            child: MaterialButton(
              child: Text('Click me'),
              color: Colors.red,
              onPressed: () {
                setState(() {
                  count++;
                });
              },
            ),
          ),
          MyListWidget(count, key: ValueKey(count)),
        ],
      ),
    );
  }
}

Using a ValueKey in this example means the state will only be recreated if count is actually different.

Alternatively, you can listen to widget changes in State.didUpdateWidget, where you can compare the current this.widget with the passed in oldWidget and update the state if necessary.

Share:
1,919
Damien
Author by

Damien

Updated on December 31, 2022

Comments

  • Damien
    Damien over 1 year

    Let's suppose that I have a Main screen (stateful widget) where there is a variable count as state. In this Main screen there is a button and another stateful widget (let's call this MyListWidget. MyListWidget initialize it's own widgets in the initState depending by the value of the count variable. Obviously if you change the value of count and call SetState, nothing will happen in MyListWidget because it create the values in the initState. How can I force the rebuilding of MyListWidget? I know that in this example we can just move what we do in the initState in the build method. But in my real problem I can't move what I do in the initState in the build method.

    Here's the complete code example:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key? key}) : super(key: key);
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int count = 5;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            body: Row(
          children: [
            Expanded(
              child: MaterialButton(
                child: Text('Click me'),
                color: Colors.red,
                onPressed: () {
                  setState(() {
                    count++;
                  });
                },
              ),
            ),
            MyListWidget(count),
          ],
        ));
      }
    }
    
    class MyListWidget extends StatefulWidget {
      final int count;
    
      const MyListWidget(this.count, {Key? key}) : super(key: key);
    
      @override
      _MyListWidgetState createState() => _MyListWidgetState();
    }
    
    class _MyListWidgetState extends State<MyListWidget> {
      late List<int> displayList;
    
      @override
      void initState() {
        super.initState();
        displayList = List.generate(widget.count, (int index) => index);
      }
    
      @override
      Widget build(BuildContext context) {
        return Expanded(
          child: ListView.builder(
            itemBuilder: (BuildContext context, int index) => ListTile(
              title: Text(displayList[index].toString()),
            ),
            itemCount: displayList.length,
          ),
        );
      }
    }
    
    
  • Damien
    Damien over 2 years
    Thank you, it was so easy!
  • gurnisht
    gurnisht about 2 years
    Elte Hupkes's answer should be the accepted one
  • Karolina Hagegård
    Karolina Hagegård about 2 years
    And how would I update the state in the didUpdateWidget method? setState() or? Because I don't feel that solves the problem... The problem is that I need to update the variables that were initiated when the widget was created, and they are not in the build() method, which is what is called when setState() fires...
  • Elte Hupkes
    Elte Hupkes about 2 years
    @KarolinaHagegård setState() would be redundant, because Flutter will always call build() after didUpdateWidget() (as the documentation I linked to mentions). Inside didUpdateWidget() you can simply update whatever local / state variables are present. For this question's example, you would probably set displayList = List.generate(newWidget.count, ...etc) in didUpdateWidget(), but you can just inspect the widget and see what needs to be done in your case. Whatever you did in initState() you could do again in didUpdateWidget(). It's not often appropriate, but it happens.
  • Karolina Hagegård
    Karolina Hagegård about 2 years
    No, actually, that didn't do it. 😏 (And yes, I saw that setState() shouldn't be used in didUpdateWidget() after I posted my comment.) It didn't do it because the value that needed to update was final, as they have to be in the widget (unlike in the State). So what I need is for the StatefulWidget to be destroyed and rebuilt again. Is there a way to do that from within didUpdateWidget()? In any case, you'll be happy to know that the ValueKey() thing did the trick! 😊 So my problem is actually solved now, and now I'm just asking out of curiosity.
  • Karolina Hagegård
    Karolina Hagegård about 2 years
    I actually tried sending the new widget as argument to the super method: super.didUpdateWidget(this.widget) but even that didn't work!... Which I think is weird... To give you the idea: This StatefulWidget is inside a list element. When a new list element comes in from an online stream listener, all the list elements shift upwards to display the new element at the bottom... but the StatefulWidgets stay in the same place! They refuse to move, but claim that they belong to the list element above... 🤦🏻‍♀️ Solved with ValueKey as mentioned, but I'm curios if didUpdateWidget() could do it too.
  • Karolina Hagegård
    Karolina Hagegård about 2 years
    The reason why this doesn't work is that when the parent of a StatefulWidget rebuilds, the StatefulWidget reuses everything that's not inside its State widget. StatelessWidgets are destroyed completely and rebuilt anew, but StatefulWidgets are only partly rebuilt. See Elte Hupkes' answer above! Using the Key key property when calling the StatefulWidget did it for me.
  • Karolina Hagegård
    Karolina Hagegård about 2 years
    Btw (sorry for writing a lot... 😁 but) it would be great if you could update your answer to just show how to use the Key key property in the constructor of the StatefulWidget as well! I figured it out, but still. It's a bit tricky the first time. In any case, thanks for your help! 🙂
  • Elte Hupkes
    Elte Hupkes about 2 years
    It's hard to comment without knowing your particular problem, but this is what's important: if a build call rebuilds the widget tree, the Widget instances all get replaced regardless (except for const instances). State instances however are kept if they're in the same place in the tree, they get didUpdateWidget() and then build() instead. If you change a StatefulWidget's widget's key, it signals that the whole thing, including the state should be different. I'd be happy to explain further in SO chat, not really enough character space here.
  • Elte Hupkes
    Elte Hupkes almost 2 years
    @KarolinaHagegård I actually missed one of your comments the last time I came here, so to answer the first one after my response for completeness: to recreate the state of a StatefulWidget, setting the key is the right approach. This will tell Flutter "even though this widget is of the same type as the widget that was here before, it's actually a different widget entirely". That will cause it to recreate it from scratch with a new state. I'm sure you'd figured that out already, though :).
  • Karolina Hagegård
    Karolina Hagegård almost 2 years
    I did. 🙂 But thanks, though! Your Key suggestion was invaluable! And this last way of explaining what it does is actually great... You should put that in the Answer!