Код-ревью Middle Виджеты и три дерева обновлено

Код-ревью: список задач

Вопрос

Виджет списка задач. Найдите проблемы с деревом, ключами и контекстом.

class TodoList extends StatefulWidget {
  List<Todo> todos;
  TodoList(this.todos);
  @override
  State<TodoList> createState() => _TodoListState();
}

class _TodoListState extends State<TodoList> {
  @override
  Widget build(BuildContext context) {
    return Column(children: [
      for (var i = 0; i < widget.todos.length; i++)
        TodoTile(key: ValueKey(i), todo: widget.todos[i], onDelete: () async {
          await api.delete(widget.todos[i].id);
          setState(() => widget.todos.removeAt(i));
          ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Удалено')));
        }),
      Text('Всего: ${widget.todos.length}', key: UniqueKey()),
    ]);
  }
}

Коротко

Корень проблем: состояние списка лежит в виджете и мутируется. Исправление: список в State (или в провайдере), удаление через новый список, ValueKey(todo.id), проверка if (!mounted) return; после await, ListView.builder для длинных списков, никаких ключей ради ключей.

Что должен найти ревьюер

  1. Ошибкастрока 2
    Изменяемое поле в виджете, который должен быть неизменяемым: список мутируется через widget.todos.removeAt, а родитель об этом не знает. Состояние должно жить в State или в стейт-менеджере.
  2. Ошибкастрока 13
    Ключ по индексу: после удаления состояния TodoTile сдвинутся на соседей. Нужен ValueKey(todo.id).
  3. Ошибкастрока 14
    После await нет проверки mounted: setState и context могут использоваться после удаления виджета.
  4. Производительностьстрока 12
    Column строит все элементы сразу: для длинного списка нужен ListView.builder.
  5. Производительностьстрока 18
    UniqueKey() в build пересоздаёт элемент Text на каждом перестроении. Ключ здесь не нужен.
  6. Стильстрока 3
    Нет const-конструктора и super.key: виджет нельзя создать как const и передать ключ.

Разбор

Корень проблем: состояние списка лежит в виджете и мутируется. Исправление: список в State (или в провайдере), удаление через новый список, ValueKey(todo.id), проверка if (!mounted) return; после await, ListView.builder для длинных списков, никаких ключей ради ключей.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.