Изменения пользовательского интерфейса в ListView.builder после удаления элемента

#flutter #dart

#flutter #dart

Вопрос:

У меня есть список StatefulWidgets (контейнеров). В этих контейнерах изображены участники курса.

Пользователь может удалить этих участников, нажав на плоскую кнопку.

Поскольку список извлекается непосредственно из Firestore в виде потока, при нажатии на плоскую кнопку и удалении возникает небольшая задержка.

В этом случае я хочу отключить кнопку до тех пор, пока этот контейнер не будет удален. Я делаю это, оборачивая контейнер указателем поглощения и редактируя значение параметра поглощения логическим значением, которое изменяется в пределах setState.

Проблема возникает после обновления списка. Значение поглощения для определенного контейнера остается истинным. Таким образом, пользователь не может удалить другого участника, который занял место в списке старого удаленного участника.

Как я могу изменить значение disabled на false после уменьшения длины списка участников?

 class _ParticipantList extends StatelessWidget {
  final CourseEventFormBloc courseEventFormBloc;

  const _ParticipantList({
    Key key,
    this.courseEventFormBloc,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<CourseEventFormBloc, CourseEventFormState>(
      builder: (context, state) {
        return Expanded(
          child: ListView.builder(
            itemCount: state.participants.length,
            itemBuilder: (context, index) {
              return _ParticipantContainer(
                index: index,
                courseEventFormBloc: courseEventFormBloc,
              );
            },
          ),
        );
      },
    );
  }
}

class _ParticipantContainer extends StatefulWidget {
  final CourseEventFormBloc courseEventFormBloc;
  final int index;
  const _ParticipantContainer({
    Key key,
    @required this.index,
    @required this.courseEventFormBloc,
  }) : super(key: key);

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

class __ParticipantContainerState extends State<_ParticipantContainer> {
  bool disabled = false;

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<CourseEventFormBloc, CourseEventFormState>(
      builder: (context, state) {
        print('${widget.index} - disabled:$disabled');
        return AbsorbPointer(
          absorbing: disabled,
          child: Container(
            margin: const EdgeInsets.symmetric(vertical: 4.0),
            decoration: BoxDecoration(
              color: Colors.blueGrey.shade100,
              borderRadius: BorderRadius.all(Radius.circular(13)),
            ),
            width: MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height * 0.07,
            alignment: Alignment.center,
            child: Stack(
              // mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Align(
                  alignment: Alignment.center,
                  child: Text(state.participants[widget.index].name),
                ),
                SizedBox(width: 20.0),
                Align(
                  alignment: Alignment.centerLeft,
                  child: FlatButton(
                    onPressed: () {
                      // through this AlertDialog I can delete a participant
                      // showAlertDialog(context, index, state.participants[index]);
                      showDialog(
                          context: context,
                          builder: (BuildContext context) {
                            return AlertDialog(
                              //! I might get an error for the Bloc since the Dialog has another context
                              title: BlocBuilder<CourseEventFormBloc,
                                  CourseEventFormState>(
                                cubit: widget.courseEventFormBloc,
                                builder: (context, state) {
                                  return Text(state.participants.isNotEmpty
                                      ? state.participants[widget.index].name
                                      : '');
                                },
                              ),
                              content:
                                  Text('Möchtest du den Teilnehmer entfernen?'),
                              actions: <Widget>[
                                FlatButton(
                                  onPressed: () => Navigator.pop(context),
                                  child: Text(
                                    'nein',
                                    style: TextStyle(color: kDarkRosaColor),
                                  ),
                                ),
                                FlatButton(
                                  onPressed: () {
                                    widget.courseEventFormBloc.add(
                                      CourseEventAdminParticipantDeleted(
                                        widget.index,
                                        state.participants[widget.index],
                                        state.courseEventObject,
                                      ),
                                    );
                                    setState(() {
                                      disabled = true;
                                    });
                                    Navigator.pop(context);
                                  },
                                  child: Text(
                                    'ja',
                                    style: TextStyle(color: kDarkRosaColor),
                                  ),
                                ),
                              ],
                            );
                          });
                    },
                    child: Container(
                      // margin: const EdgeInsets.only(right: 27.0),
                      decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(100),
                          border: Border.all(width: 2, color: kDarkRosaColor)),
                      child: Icon(
                        Icons.delete,
                        color: kDarkRosaColor,
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}
  

Имеет ли смысл использовать это перед build методом виджета с отслеживанием состояния?

 @override
  void didUpdateWidget(covariant _ParticipantContainer oldWidget) {
    super.didUpdateWidget(oldWidget);
    disabled = false;
  }
  

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

1. Не можете ли вы создать обратный вызов, т.е. когда удаление завершено. внутри этой функции обратного вызова вы можете использовать setSate и снова присвоить false вашему отключенному?

Ответ №1:

Создайте ключ для каждого элемента списка, чтобы платформа не могла повторно использовать неправильное состояние.

На самом деле это и есть цель неглобальных ключей: различать дочерние элементы, которые в противном случае стали бы неразличимыми в процессе согласования. https://api.flutter.dev/flutter/foundation/Key-class.html

Ключи могут быть сгенерированы из любого свойства, подобного UUID, в вашей participant базе данных (идентификатор участника или просто имя) ValueKey() .

   @override
  Widget build(BuildContext context) {
    return BlocBuilder<CourseEventFormBloc, CourseEventFormState>(
      builder: (context, state) {
        return Expanded(
          child: ListView.builder(
            itemCount: state.participants.length,
            itemBuilder: (context, index) {
              return _ParticipantContainer(
                key: ValueKey(state.participants[index].id), // Generate ValueKey based on UUID
                index: index, // Perhaps you should pass entire participant instead of an index
                courseEventFormBloc: courseEventFormBloc,
              );
            },
          ),
        );
      },
    );
  }
  

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

Ответ №2:

Поскольку я использую Bloc , в конце концов, я добавил a String deletedId в класс состояния. Когда я удаляю участника, я просто передаю его uid во вновь созданное состояние.

Кроме того, я избавился от виджета с отслеживанием состояния и выполняю следующую проверку, чтобы запретить клики по только что удаленному участнику.

absorbing: state.deleteId == state.participants[index].uid,

Подход, рекомендованный @First_Strike, работает аналогично, только он будет использовать другое управление состоянием (виджет с отслеживанием состояния, а не блок).

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

1. Я обновил свой ответ, чтобы продемонстрировать, как использовать ключ.