fluttersetstateflutter-widgetstatefulwidgetstatelesswidget

Child Property in Stateful Widget


I've made some test widgets to illustrate a point that I'm having difficulty with in a much more complicated widget.

I have the following widget:

class TestListWidget extends StatefulWidget {
  Widget child;

  TestListWidget({Widget child}) {
    this.child = child;
  }

  @override
  State<StatefulWidget> createState() {
    return TestListWidgetState();
  }


}
class TestListWidgetState extends State<TestListWidget>
{
  Widget child;

  int buttonCount = 0;

  @override initState() {
    child = widget.child;
  }

  _clickedCountButton()
  {
    setState(() {
      buttonCount++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Column(children: [
      Text("Times Hit: $buttonCount"),
      ElevatedButton(onPressed: _clickedCountButton, child: Text("Update Count")),
      child
    ]);
  }
}

The above widget is being used inside the following widget:

class TestList extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new TestListState();
  }
}

class TestListState extends State<TestList> {
  String _testStr = "not clicked";

  _clickButton()
  {
    setState(() {
      _testStr = "CLICKED";
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: "Test",
        theme: ThemeData(
        primarySwatch: Colors.blue
    ),
    home:Scaffold(
        body : testListWidget(child: Row(children: [
      Text(_testStr),
      ElevatedButton(onPressed: _clickButton, child: Text("Click me!"))
    ]))));
  }
}

The issue I'm having is when the "Click me!" button is clicked, the function is called, but the text on the screen is not updated to "CLICKED!". The "Update Count" button works as intended though.

If I make the TestListWidget a stateless widget (and remove the update count button functionality) then the "Click Me!" button works as expected. Is there any way to make a child widget rebuild when passed to a stateful widget?


Solution

  • Your problem is quite simple. You have two variables, one is TestListWidget.child, we'll call it stateful widget's child. The second is TestListWidgetState.child, we'll call it state's child.

    You make state's child to be equal to stateful widget's child on initState, but initState only runs when you first create a state, so updating the stateful widget's child will not update the state's child because initState won't run again.

    To fix this, I believe you can just completely remove state's child, and use widget.child instead:

    return new Column(children: [
          Text("Times Hit: $buttonCount"),
          ElevatedButton(onPressed: _clickedCountButton, child: Text("Update Count")),
          widget.child
        ]);
    

    Full example:

    import 'package:flutter/material.dart';
    
    void main() => runApp(TestList());
    
    class TestListWidget extends StatefulWidget {
      Widget child;
    
      TestListWidget({required this.child});
    
      @override
      State<StatefulWidget> createState() {
        return TestListWidgetState();
      }
    
    
    }
    class TestListWidgetState extends State<TestListWidget>
    {
    
      int buttonCount = 0;
    
      _clickedCountButton()
      {
        setState(() {
          buttonCount++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return new Column(children: [
          Text("Times Hit: $buttonCount"),
          ElevatedButton(onPressed: _clickedCountButton, child: Text("Update Count")),
          widget.child
        ]);
      }
    }
    
    class TestList extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return new TestListState();
      }
    }
    
    class TestListState extends State<TestList> {
      String _testStr = "not clicked";
    
      _clickButton()
      {
        setState(() {
          _testStr = "CLICKED";
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            title: "Test",
            theme: ThemeData(
            primarySwatch: Colors.blue
        ),
        home:Scaffold(
            body : TestListWidget(child: Row(children: [
          Text(_testStr),
          ElevatedButton(onPressed: _clickButton, child: Text("Click me!"))
        ]))));
      }
    }