flutterdartstateful

Force rebuild of a stateful child widget in flutter


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,
      ),
    );
  }
}


Solution

  • 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.