Flutter: Periodically calling list.add() inside initState() causes build() to run but the updated List isn't shown on screen

Viewed 106

On the screen I expect to see an ever growing list:

  • 0
  • 1
  • 2
  • ...

but it's just black. If I hit CTRL+S in Android Studio all of a sudden the whole list is finally shown.

import 'dart:async';

import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  List<Widget> list = [];

  @override
  Widget build(BuildContext context) {
    print('${list.length}');
    return MaterialApp(
      home: ListView(
        children: list,
      ),
    );
  }

  @override
  void initState() {
    super.initState();

    new Timer.periodic(const Duration(seconds: 1), (timer) {
      setState(() {
        list.add(Text('${list.length}', key: Key(list.length.toString())));
      });
    });
  }
}

At first I thought it was because I wasn't setting key property, but I added that and still nothing.

I know the build() property is being triggered every second because I see the print() statement showing the numbers in the debug Run tab output.

What am I missing here? It's got to be something simple!

2 Answers

Interesting discovery.

What is actually causing your UI to not update is that the reference to the list variable is staying the same.

Adding an element, doesn't change the reference and ListView seems to work based on references.

Two ways you can confirm this are.

  1. Change your ListView to a much simpler Column widget and voila, it's updating correctly.

  2. Change your updation method. Destroy the reference and make a new List like this,

    list = [...list, Text('${list.length}')];
    

    and yet again, surprise.

You need to 'key' parameter to 'ListView' widget.
With below code, I confirmed working what you want.
I think that because you add a 'key' to Text widget, 'ListView' widget didn't rebuild.

import 'dart:async';

import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  List<Widget> list = [];

  @override
  Widget build(BuildContext context) {
    print('${list.length}');
    return MaterialApp(
      home: ListView(
        key: UniqueKey(),
        children: list,
      ),
    );
  }

  @override
  void initState() {
    super.initState();

    new Timer.periodic(const Duration(seconds: 1), (timer) {
      setState(() {
        list.add(Text('${list.length}', key: Key(list.length.toString())));
      });
    });
  }
}

Or you can do it also by changing 'ListView' to 'ListView.builder'.

ListView.builder(
        itemCount: list.length,
        itemBuilder: (context, index) {
          return list[index];
        },
      ),
    );
Related