Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter setState changing, but not rerendering

I've created a simple screen that takes a List of letters and renders them in a grid. I have a button with a shuffle method that shuffles this list. Inside my build method, I see that the state is getting updated with the new list and is printing out a shuffled list each time the button is pressed, but the screen doesn't change.

class _LetterContainerState extends State<LetterContainer> {
  List<String> _letters = ['D', 'A', 'B', 'C', 'E', 'F', 'G', 'H'];



  void shuffle() {
    var random = new Random();
    List<String> newLetters = _letters;
    for (var i = newLetters.length - 1; i > 0; i--) {
      var n = random.nextInt(i + 1);
      var temp = newLetters[i];
      newLetters[i] = newLetters[n];
      newLetters[n] = temp;
    }

    setState(() {
      _letters = newLetters;
    });
  }

  @override
  Widget build(BuildContext context)  {
    print('LETTERS');
    print(_letters);
    List<LetterTile> letterTiles =
        _letters.map<LetterTile>((letter) => new LetterTile(letter)).toList();

    return new Column(
      children: <Widget>[
        new FlatButton(onPressed: shuffle, child: new Text("Shuffle")),
        new Container(

            color: Colors.amberAccent,
            constraints: BoxConstraints.expand(height: 200.0),
            child: new GridView.count(
              crossAxisCount: 4,
              mainAxisSpacing: 4.0,
              crossAxisSpacing: 4.0,
              children: letterTiles,
            ))
      ],
    );
  }
}

EDIT:

import 'package:flutter/material.dart';

class Vowels {
  static const values = ['A', 'E', 'I', 'O', 'U'];

  static bool isVowel(String letter) {
    return values.contains(letter.toUpperCase());
  }
}

class LetterTile extends StatefulWidget {
  final String value;
  final bool isVowel;

  LetterTile(value)
            : value = value,
              isVowel = Vowels.isVowel(value);

  @override
  _LetterTileState createState() => new _LetterTileState(this.value);

}

class _LetterTileState extends State<LetterTile> {
  _LetterTileState(this.value);

  final String value;

  @override
  Widget build(BuildContext context) {
    Color color = Vowels.isVowel(this.value) ? Colors.green : Colors.deepOrange;
    return new
    Card(
      color: color,
      child: Padding(
        padding: EdgeInsets.all(8.0),
        child: Text(
          this.value,
          style: TextStyle(fontSize: 40.0, color: Colors.white)
        )
      )
    );

  }

}
like image 381
Cameron Sima Avatar asked Dec 13 '22 14:12

Cameron Sima


1 Answers

If you replace your example LetterTile widget with a Text widget, the shuffling will work again. The reason this is not working is that a State object is only created the first time a widget is instantiated. So by passing the value directly to the state, you ensure that it never updates. Instead reference the value via widget.value:

class LetterTile extends StatefulWidget {
  final String value;
  final bool isVowel;

  LetterTile(this.value) : isVowel = Vowels.isVowel(value);

  @override
  _LetterTileState createState() => new _LetterTileState();
}

class _LetterTileState extends State<LetterTile> { 
  @override
  Widget build(BuildContext context) {
    Color color = Vowels.isVowel(widget.value) ? Colors.green : Colors.deepOrange;
    return Card(
      color: color,
      child: Padding(
        padding: EdgeInsets.all(8.0),
        child: Text(
          widget.value,
          style: TextStyle(fontSize: 40.0, color: Colors.white)
        )
      )
    );
  }
}

Edit: Some more explanation.

The point of a State object is that it is persistent across builds. The first time you build a particular LetterTile widget, this also creates a new State object. The second time build is called, the framework finds the existing State object and reuses it. This is how you can have resources like timers, network requests, and other bound to an immutable tree of widgets.

In your case, since you passed the letter to the State object, each one would stay associated with whatever the first passed letter was. Instead, by reading them off the widget you always receive the most up to date data when the widget associated with the State object is replaced.

like image 199
Jonah Williams Avatar answered Dec 28 '22 18:12

Jonah Williams