2
votes

I followed example app on Flutter https://codelabs.developers.google.com/codelabs/first-flutter-app-pt2/#0

and noticed that every time i click the row (make wordpair favourite and display red heart next to it) whole listview rebuilds. Thought it might be inefficient to rebuild whole list so i modified original code from the example so that only the tile state changes and thought that would make only the tile to be redrawn. I see the state changes but the tile is not being redrawn/rebuild

Now as i think of it it might have no sense as the tile size might change during update and it would require listview to be rebuild anyway but my questions is:

What exactly prevents the tile from being rebuild/redrawn if setState() call proceed as normal and the tile is being marked as dirty?

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "This is the title of the App",
      home: RandomWords(),
    );
  }
}

class RandomWordsState extends State<RandomWords> {
  final List<WordPair> _suggestions = <WordPair>[];
  final Set<WordPair> _saved = Set<WordPair>();

  @override
  Widget build(BuildContext context) {
    print("dupa scaffold");
    return new Scaffold(
      appBar: AppBar(
        title: Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(),
    );
  }

  Widget _buildSuggestions() {
    print("dupa listview");
    return ListView.builder(
      padding: const EdgeInsets.all(16.0),
      itemBuilder: (BuildContext _context, int i) {
        if (i.isOdd) {
          return new Divider();
        }
        final int index = i ~/ 2;

        if (index >= _suggestions.length) {
          _suggestions.addAll(generateWordPairs().take(10));
        }

        return _buildRow(_suggestions[index]);
      },
    );
  }

  Widget _buildRow(WordPair wordPair) {
    print ("build row");
    return MyListTile(wordPair, _saved.contains(wordPair), () {
      print("$wordPair is saved ${_saved.contains(wordPair)}");
      final isSaved = _saved.contains(wordPair);
      isSaved ? _saved.remove(wordPair) : _saved.add(wordPair);
    });
  }
}

class MyListTile extends StatefulWidget {

  final MyListTileState _state;

  MyListTile(WordPair wordPair, bool isSaved, Function stateChangeOnTap):
    _state = MyListTileState(wordPair, isSaved, stateChangeOnTap);

  @override
  State<StatefulWidget> createState() => _state;
}

class RandomWords extends StatefulWidget {
  @override
  RandomWordsState createState() => new RandomWordsState();

}

class MyListTileState extends State<MyListTile> {

  final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
  WordPair wordPair;
  bool isSaved;
  Function stateChangeOnTap;


  MyListTileState(this.wordPair, this.isSaved, this.stateChangeOnTap);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new ListTile(
      title: Text(
        wordPair.asPascalCase,
        style: _biggerFont,
      ),
      trailing: new Icon(
        isSaved ? Icons.favorite : Icons.favorite_border,
        color: isSaved ? Colors.red : null,
      ),
      onTap: () {
        setState(stateChangeOnTap);
      },
    );
  }

}
2
I think the problem is that you are trying to update the state of RandomWordsState, but you are calling setState() in MyListTile. - alex kucksdorf

2 Answers

2
votes

In this example you save selected tile in _saved, so it changes the whole RandomWordsState. But if you don't need such things - you can avoid refreshing of whole list

2
votes

The problem was quite simple and I should've noticed that immediately. isSaved variable did not reflect _saved state. It was initialised during object creation and that's it. So when the _saved state was being updated isSaved had still initial value. I've solved it putting there a function that returns current wordpair state when MyListTileState build() method is called.

It turned out you can rebuild just a single element in ListView without any problem, even if you change the size of an element. Works perfectly.