12

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
  • 921
  • 4
  • 13
  • 31

2 Answers2

30

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.

Elte Hupkes
  • 2,773
  • 2
  • 23
  • 18
  • 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... – Karolina Hagegård Apr 27 '22 at 14:52
  • @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. – Elte Hupkes Apr 28 '22 at 08:21
  • 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 Apr 28 '22 at 08:34
  • 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 Apr 28 '22 at 08:47
  • 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! – Karolina Hagegård Apr 28 '22 at 08:59
  • 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 Apr 28 '22 at 11:25
  • 1
    @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 :). – Elte Hupkes May 18 '22 at 04:58
  • 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! – Karolina Hagegård May 21 '22 at 05:49
  • Very sharp answer. – jbryanh Apr 27 '23 at 00:48
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,
      ],
    ));
  }
}
Anas Nadeem
  • 779
  • 6
  • 10
  • Thank you, it was so easy! – Damien Sep 02 '21 at 18:31
  • 1
    Elte Hupkes's answer should be the accepted one – gurnisht Feb 24 '22 at 15:07
  • 2
    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 Apr 28 '22 at 08:56