setState не перестраивает ListView.builder

#flutter #dart #flutter-layout

Вопрос:

Я пытаюсь создать страницу комментариев. Список комментариев отображается с помощью ListView.builder. И когда пользователь вводит комментарий, он снова перестраивает список, чтобы включить только что добавленный комментарий. Но почему-то список не восстанавливается, и я получаю это сообщение в терминале:

Изменение содержимого в области компоновки может привести к странному поведению метода ввода, и поэтому его не рекомендуется использовать. Видишь https://github.com/flut ter/флаттер/проблемы/78827 для получения более подробной информации

Недавно добавленный комментарий отображается только тогда, когда я закрываю страницу комментариев и снова открываю ее. Пожалуйста, помогите мне, так как я не уверен, в чем проблема и как ее решить.

Страница Комментариев:

 import 'package:flutter/material.dart';

import '../model/model_comment.dart';

class CommentsPage extends StatefulWidget {
  @override
  _CommentsPageState createState() => _CommentsPageState();
}

class _CommentsPageState extends State<CommentsPage> {
  ValueNotifier<int> _counter = ValueNotifier<int>(0);
  TextEditingController _controllerComment = TextEditingController();
  bool _hasComment = false;

  @override
  void dispose() {
    _controllerComment.dispose();
    super.dispose();
  }

  _commentOnSend() {
    setState(() {
      var value = CommentModel(
        avatarUrl: "https://randomuser.me/api/portraits/women/34.jpg",
        name: "Laurent Oslo",
        dateTime: "30 Dec 20 08:00",
        comment: _controllerComment.text,
      );
      CommentModel.dummyData.insert(0, value);
    });
    _controllerComment.clear();
    FocusScope.of(context).unfocus();
  }

  Widget _listView = ListView.builder(
    itemCount: CommentModel.dummyData.length,
    itemBuilder: (context, index) {
      CommentModel _model = CommentModel.dummyData[index];
      return Column(
        children: <Widget>[
          Divider(
            height: 12.0,
          ),
          Container(
            padding: EdgeInsets.fromLTRB(3.0, 3.0, 3.0, 3.0),
            child: ListTile(
              leading: CircleAvatar(
                radius: 24.0,
                backgroundImage: NetworkImage(_model.avatarUrl),
              ),
              title: Text(_model.name),
              subtitle: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(_model.comment),
                  Text(_model.dateTime),
                ],
              ),
            ),
          ),
        ],
      );
    },
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Comments"),
        titleSpacing: 40.0,
      ),
      body: Container(
        child: Column(
          children: [
            Expanded(child: _listView),
            Divider(
              height: 1.0,
            ),
            ListTile(
              leading: Container(
                height: 40.0,
                width: 40.0,
                decoration: BoxDecoration(
                    color: Colors.deepPurple,
                    borderRadius: BorderRadius.all(Radius.circular(50.0))),
                child: CircleAvatar(
                    radius: 50.0,
                    backgroundImage: NetworkImage(
                        "https://randomuser.me/api/portraits/men/83.jpg")),
              ),
              title: TextField(
                  decoration: (InputDecoration(
                    hintText: "Add Comment"
                  )),
                  minLines: 1,
                  maxLines: 5,
                  controller: _controllerComment,
                  onChanged: (val) {
                    setState(() {
                      _counter.value  = 1;
                      if (val.isNotEmpty) {
                        _hasComment = true;
                      } else {
                        _hasComment = false;
                      }
                    });
                  }),
              trailing: ValueListenableBuilder(
                valueListenable: _counter,
                builder: (BuildContext context, int value, Widget? child) {
                  return IconButton(
                    onPressed: _hasComment
                        ? () {
                            _commentOnSend();
                          }
                        : null,
                    icon: Icon(Icons.send_sharp,
                        color: _hasComment ? Colors.deepPurple : null),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}
 

Класс модели Комментариев:

 class CommentModel {
  final String avatarUrl;
  final String name;
  final String dateTime;
  final String comment;

  CommentModel(
      {required this.avatarUrl,
      required this.name,
      required this.dateTime,
      required this.comment});

  static List<CommentModel> dummyData = [
    CommentModel(
      avatarUrl: "https://randomuser.me/api/portraits/women/34.jpg",
      name: "Laurent Oslo",
      dateTime: "30 Dec 20 08:00",
      comment:
          "There is a reason why I implemented it like this. In a comment section, the same comment widget can appear multiple times. So, the keys assigned to each widget needs to be different. Otherwise I won’t be able to refer to a specific widget later on",
    ),
    CommentModel(
      avatarUrl: "https://randomuser.me/api/portraits/women/49.jpg",
      name: "Tracy Wilbur",
      dateTime: "01 Oct 20 17:00",
      comment: "First Comment!",
    ),
    CommentModel(
      avatarUrl: "https://randomuser.me/api/portraits/women/23.jpg",
      name: "Michael Scott",
      dateTime: "30 Sept 20 06:00",
      comment:
          "The idea is simple. Use the prefix with something else to make the key unique. In this case, I’ve used the index value to make them unique. I used the keys in line 25, 51, 56, and 60. See how I’ve done it in these lines.",
    ),
    CommentModel(
      avatarUrl: "https://randomuser.me/api/portraits/men/45.jpg",
      name: "Williams John",
      dateTime: "17 Sept 20 02:00",
      comment: "Join!",
    ),
    CommentModel(
      avatarUrl: "https://randomuser.me/api/portraits/women/77.jpg",
      name: "Claire Rach",
      dateTime: "15 Aug 20 19:00",
      comment:
          "I want the comment section to be hidden away. A user can view comments by tapping to expand a widget. Meaning, the comment section should be collapsible. It will toggle between expanded and collapsed mode when being tapped.",
    ),
    CommentModel(
      avatarUrl: "https://randomuser.me/api/portraits/men/81.jpg",
      name: "Joe Panama",
      dateTime: "05 Jul 20 03:00",
      comment:
          "A comment will have 3 data values which are commenting user details, time of comment posting and the actual text of the comment. I’ve created a “CommentModel” class to create this model.",
    ),
    CommentModel(
      avatarUrl: "https://randomuser.me/api/portraits/men/83.jpg",
      name: "Mark Hamill",
      dateTime: "09 Jun 20 15:00",
      comment:
          "Because comments are part of a post, “PostModel” needs to have a list of comment data. So I’ve modified “PostModel” to have a list of “CommentModel” objects. Refer to the code changes to see what I’ve done.",
    ),
    CommentModel(
      avatarUrl: "https://randomuser.me/api/portraits/men/85.jpg",
      name: "Williams Dafoe",
      dateTime: "25 May 20 20:00",
      comment:
          "Notice lines 18 to 29. I’ve used the “ExpansionTile” widget to create a collapsible list of comments. Each comment is a “_SingleComment” widget implemented in lines 34 to 67.",
    ),
    CommentModel(
      avatarUrl: "https://randomuser.me/api/portraits/men/98.jpg",
      name: "Phillips Mach",
      dateTime: "01 Apr 20 17:00",
      comment:
          "New to app development and flutter in general(high schooler). Can I use this template? Do I have to give credit or can I just use it? At the very least, can I see the source code so I can learn from it?",
    ),
    CommentModel(
      avatarUrl: "https://randomuser.me/api/portraits/men/12.jpg",
      name: "Joe Snowden",
      dateTime: "04 Mar 20 16:00",
      comment: "PM ME!",
    ),
  ];
}
 

Ответ №1:

Это связано с тем, что вы вводите переменную Listview.builder состояния, что является ожидаемым поведением, поскольку переменные состояния не инициализируются повторно при перестроении.

Если вы хотите провести рефакторинг, вы можете создать новую функцию, чтобы вернуть ее:

 ListView getList(){
  return ListView.builder(
    itemCount: CommentModel.dummyData.length,
    itemBuilder: (context, index) {
      CommentModel _model = CommentModel.dummyData[index];
      return Column(
        children: <Widget>[
          Divider(
            height: 12.0,
          ),
          Container(
            padding: EdgeInsets.fromLTRB(3.0, 3.0, 3.0, 3.0),
            child: ListTile(
              leading: CircleAvatar(
                radius: 24.0,
                backgroundImage: NetworkImage(_model.avatarUrl),
              ),
              title: Text(_model.name),
              subtitle: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(_model.comment),
                  Text(_model.dateTime),
                ],
              ),
            ),
          ),
        ],
      );
    },
  );
}
 

Комментарии:

1. Хотя подход верен и проще для автора, возвращать виджеты из функций не рекомендуется. Лучше переработать представление списка в его собственный CommentList виджет без состояния.

2. @Абдуррафайсалем, это правда. Спасибо, что предупредили.