Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter ListView doesn't change when setState() is called

Tags:

flutter

dart

I'm new to using Flutter/Dart and I'm having a hard time understanding why the following code doesn't update the ListView contents when _updateResults() calls setState(). I've even added a print statement to display the contents of _results and I can see that items are being added to the list but the display never updates.

class SearchHomePage extends StatefulWidget {
  SearchHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _SearchHomePageState extends State<SearchHomePage> {
  List<Widget> _results = <Widget>[];

  @override
  void initState() {
    super.initState();
    _results.add(Text("testing"));
  }

  void _updateResults(String text) {
    setState(() {
      _results.add(Text(text));
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(children: [
        Container(
          child: TextField(
            onSubmitted: (String text) {
              _updateResults(text);
            },
          ),
        ),
        Expanded(
          child: ListView(children: _results)
        ),
      ]),
    );
  }
}

If I change the ListView portion to be like:

Expanded(
   child: ListView(children: _results.toList())
)

Then it will work and I'm not sure why because it was already a list. What exactly is going on here and why doesn't it work with just ListView?

like image 543
Josh Avatar asked Oct 16 '22 10:10

Josh


2 Answers

From StatefulWidget documentation:

StatefulWidget instances themselves are immutable and store their mutable state either in separate State objects that are created by the createState method, or in objects to which that State subscribes [...]

From this article:

By definition, immutable means that, once created, an object/variable can’t be changed. So, instead of changing a property of an object, you have to make a copy (or clone) of the entire object and in the process, change the property in question.

You created the ListView with the same array. You changed the content of the array, but you did not change the reference to that object. That's why it works when you use _results.toList() because .toList() "creates a [List] containing the elements of this [Iterable]".

Another solution could be:

setState(() {
  _results = List.from(_results)
    ..add(Text(text));
});
like image 197
alecsam Avatar answered Oct 19 '22 01:10

alecsam


it doesn't work because you are adding a new item to the existing list and not creating a new list.

this will fix your problem:


class SearchHomePage extends StatefulWidget {
  SearchHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _SearchHomePageState extends State<SearchHomePage> {
  List<Widget> _results = <Widget>[];

  @override
  void initState() {
    super.initState();
    _results.add(Text("testing"));
  }

  void _updateResults(String text) {
    setState(() {
      _results = [..._results, Text(text)];
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(children: [
        Container(
          child: TextField(
            onSubmitted: (String text) {
              _updateResults(text);
            },
          ),
        ),
        Expanded(
            child: ListView(children: _results)
        ),
      ]),
    );
  }
}

note: it's better to do the follwing rather than creating widgets and saving them in an array:


class SearchHomePage extends StatefulWidget {
  SearchHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _SearchHomePageState extends State<SearchHomePage> {
  List<String> _results = <String>[];

  @override
  void initState() {
    super.initState();
    _results.add("testing");
  }

  void _updateResults(String text) {
    setState(() {
      _results = [..._results, text];
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(children: [
        Container(
          child: TextField(
            onSubmitted: (String text) {
              _updateResults(text);
            },
          ),
        ),
        Expanded(
            child: ListView(
          children: _results.map((v) => Text(v)).toList(),
        )),
      ]),
    );
  }
}

when you call .toList() it will create a new list instance (copy of your old list)

like image 21
Sahandevs Avatar answered Oct 19 '22 00:10

Sahandevs