Search code examples
flutterdartflutter-layout

state seems to switch from widget to widget


I was building a todo list app which uses a ListView.builder to render multiple task widgets which have three properties, title, checked, and starred. It works just fine until I star/check an existing task and add a new task, in which case the state of the previous task seems to jump to the newly added task. What could be causing the problem? Could this have something to do with keys?

class Main extends StatefulWidget {
  @override
  State<Main> createState() => _MyAppState();
}

class _MyAppState extends State<Main> {
  var todoList = [];
  var stars = 0;
  void addStar() {
    setState(() {
      stars++;
    });
  }

  void removeStar() {
    if (stars > 0) {
      setState(() {
        stars--;
      });
    }
  }

  void addTodo(title) {
    setState(() {
      todoList.add(title);
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        floatingActionButton: Builder(builder: (context) {
          return FloatingActionButton(
            child: const Icon(Icons.add),
            onPressed: () {
              showModalBottomSheet(
                  context: context,
                  builder: (BuildContext context) {
                    return NewTodo(addTodo: addTodo);
                  });
            },
          );
        }),
        body: SafeArea(
          child: Column(
            children: [
              Padding(
                padding: const EdgeInsets.all(12.0),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    const Text(
                      'My Day',
                      style:
                          TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
                    ),
                    Container(
                      child: Row(
                        children: [
                          const Text(
                            'IMPORTANT: ',
                            style: TextStyle(
                              fontSize: 16.0,
                              letterSpacing: 1.0,
                              fontWeight: FontWeight.bold,
                              color: Color.fromARGB(255, 58, 58, 58),
                            ),
                          ),
                          Text(
                            '$stars',
                            style: const TextStyle(
                                fontSize: 24.0,
                                letterSpacing: 1.0,
                                fontWeight: FontWeight.bold,
                                color: Color.fromARGB(255, 253, 147, 8)),
                          ),
                          const SizedBox(width: 20.0),
                          IconButton(
                            icon: Icon(Icons.more_vert, size: 24),
                            onPressed: () => {print('more ...')},
                          ),
                        ],
                      ),
                    )
                  ],
                ),
              ),
              Expanded(
                  // height: 300,
                  child: todoList.length == 0
                      ? Text('No Tasks Yet 💪',
                          style: TextStyle(
                              fontSize: 24.0,
                              fontWeight: FontWeight.w600,
                              color: Colors.grey))
                      : ListView.builder(
                          itemCount: todoList.length,
                          itemBuilder: (context, index) {
                            return Todo(
                                title: todoList[(todoList.length - 1) - index],
                                addStar: addStar,
                                removeStar: removeStar);
                          }))
            ],
          ),
        ),
      ),
    );
  }
}

the app currently only has three files, below is a link to the gist.

https://gist.github.com/FENETMULER/eb4a898b82f9aa4c6a871a1fa9833c84


Solution

  • The solution is to use Keys with a ListView and not a ListView.builder as it doesn't seem to work with ListView.builder, the problem actually comes from reversing the list, since when the list is reversed every time a new task is added the objects in the Widget Tree won't be in line with their corresponding Object in the Element Tree.

    ListView(children: todoList.reversed.map((title) => Todo(title: title, key: ValueKey(title))).toList())