#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. Я обновил свой ответ, чтобы продемонстрировать, как использовать ключ.