Rosanna Trevisan
Rosanna Trevisan

Reputation: 452

Flutter cannot call setState to rebuild widget

I am very new to flutter and the millions of answers I have read online made me even more confusion. This is my page:

class SecondDegreePage extends StatefulWidget {
  const SecondDegreePage({Key key}) : super(key: key);

  @override
  SecondDegreePageState createState() => SecondDegreePageState();
}

class SecondDegreePageState extends State<SecondDegreePage> {

  final GlobalKey<FormState> _formKey = new GlobalKey();

  final controllerParamA = TextEditingController();
  final controllerParamB = TextEditingController();
  final controllerParamC = TextEditingController();

  Quadratic _solver = Quadratic([
    (Random().nextInt(10) + 1) * 1.0,
    (Random().nextInt(10) + 1) * 1.0,
    (Random().nextInt(10) + 1) * 1.0
  ]);

  void updateData() {
    setState(() {
      _solver = Quadratic([
        (Random().nextInt(10) + 1) * 1.0,
        (Random().nextInt(10) + 1) * 1.0,
        (Random().nextInt(10) + 1) * 1.0
      ]);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(AppLocalization.of(context).title),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        children: <Widget>[

          // Input form
          Form(
            key: _formKey,
            child: Padding(
              padding: EdgeInsets.all(20),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  EquationInputField(
                    controller: controllerParamA,
                    textHint: 'a',
                    width: 70,
                  ),
                  EquationInputField(
                    controller: controllerParamB,
                    textHint: 'b',
                    width: 70,
                  ),
                  EquationInputField(
                    controller: controllerParamC,
                    textHint: 'c',
                    width: 70,
                  ),
                ],
              ),
            ),
          ),

          // Submit button
          Padding(
            padding: EdgeInsets.fromLTRB(0, 30, 0, 30),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                RaisedButton(
                  onPressed: () => updateData(),
                  color: Colors.blue,
                  elevation: 6,
                  child: Text(
                    AppLocalization.of(context).solve,
                    style: TextStyle(
                        color: Colors.white
                    ),
                  ),
                )
              ],
            ),
          ),

          //Solutions
          Expanded(
            child: Padding(
              padding: EdgeInsets.only(bottom: 10),
              child: AlgebraicSolutions(
                solver: _solver
                ),
              ),
            ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    controllerParamA.dispose();
    controllerParamB.dispose();
    controllerParamC.dispose();

    super.dispose();
  }
}

The type Quadratic is simply a class that does some math, nothing important. The problem is in this line:

RaisedButton(
  onPressed: () => updateData()
}

Why nothing happens? I have read thet the call to setState calld the build method and the widget is re-built. For this reason I expect that

child: AlgebraicSolutions(
  solver: _solver
),

here the _solver reference gets updated. AlgebraicSolutions is the following:

class AlgebraicSolutions extends StatefulWidget {

  final Algebraic solver;

  const AlgebraicSolutions({Key key, @required this.solver}) : super(key: key);

  @override
  AlgebraicSolutionsState createState() => AlgebraicSolutionsState();
}

class AlgebraicSolutionsState extends State<AlgebraicSolutions> {

  //'Quadratic' is a subtype of Algebraic
  Algebraic _solver;

  @override
  void initState() {
    _solver = widget.solver;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    var solutions = _solver.solve();

    return ListView.builder(
      itemCount: solutions.length,
      itemBuilder: (context, index) {
        //...
      }
    );
  }

}

Is that because I am using initState in AlgebraicSolutionsState that breaks something?

Please note that Quadratic type is subclass of Algebraic type.

Upvotes: 1

Views: 1402

Answers (1)

SpencerPark
SpencerPark

Reputation: 3506

Yep, saving the initial value in initState is what's giving you trouble. That lifecycle method is only called on first build but since the widget reconciles to the compatible type the same state is used. i.e. the properties change (widget instance is different) but the state is the same object. build gets called again but initState does not.

In this situation I would use a getter to give you the same convenience but always use the _solver from the current widget. Then you can discard the initState.

Algebraic get _solver => widget.solver;

The other option being didUpdateWidget but it really not necessary here for something so straightforward otherwise.

Upvotes: 4

Related Questions