#flutter #bloc
#флаттер #блок
Вопрос:
Прямо сейчас у меня есть приложение, которое создает список таймеров. Он использует два блока, один для отдельных таймеров и один для общего списка. Пример списка можно увидеть ниже:
На изображении вы можете видеть, что индексы 0 и 2 не были запущены, в то время как индекс 1 запущен. Моя проблема в том, что всякий раз, когда я добавляю или удаляю элемент из списка, все таймеры сбрасываются.
Есть ли способ сохранить их состояние при перерисовке родительского виджета?
Вот код списка:
Здесь плитка элемента содержит блок для таймера:
class HomePage extends StatefulWidget {
static const TextStyle timerTextStyle = TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
);
final Stream shouldTriggerChange;
HomePage({@required this.shouldTriggerChange});
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
StreamSubscription streamSubscription;
@override
initState() {
super.initState();
streamSubscription = widget.shouldTriggerChange.listen((data) {
createTimer(context, data);
});
}
void createTimer(BuildContext context, timer_type timer) {
BlocProvider.of<ListBloc>(context).add(Add(
id: DateTime.now().toString(),
duration: timer == timer_type.timer ? 100 : 0,
timer: timer,
timerTextStyle: HomePage.timerTextStyle));
}
@override
didUpdateWidget(HomePage old) {
super.didUpdateWidget(old);
// in case the stream instance changed, subscribe to the new one
if (widget.shouldTriggerChange != old.shouldTriggerChange) {
streamSubscription.cancel();
streamSubscription = widget.shouldTriggerChange
.listen((data) => createTimer(context, data));
}
}
@override
dispose() {
super.dispose();
streamSubscription.cancel();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<ListBloc, ListState>(
builder: (context, state) {
if (state is Failure) {
return Center(
child: Text('Oops something went wrong!'),
);
}
if (state is Loaded) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
state.timers.isEmpty
? Center(
child: Text('no content'),
)
: Expanded(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ItemTile(
index: index,
timer: state.timers[index],
onDeletePressed: (id) {
BlocProvider.of<ListBloc>(context)
.add(Delete(id: id));
},
);
},
itemCount: state.timers.length,
),
),
],
);
}
return Center(
child: CircularProgressIndicator(),
);
},
);
}
}
Первоначально я думал, что это ключевая проблема, потому что список удалялся неправильно, но я считаю, что я реализовал ключи так, как они показаны в примере, и у меня все еще есть проблема.
class ItemTile extends StatelessWidget {
final key = UniqueKey();
final Function(String) onDeletePressed;
final int index;
final Timer timer;
ItemTile({
Key key,
@required this.index,
@required this.timer,
@required this.onDeletePressed,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: Row(
children: [
Text('${index.toString()}'),
BlocProvider(
create: (context) => TimerBloc(ticker: Ticker(), timer: timer),
child: Container(
height: (MediaQuery.of(context).size.height - 100) / 5,
child: Row(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: Center(
child: BlocBuilder<TimerBloc, TimerState>(
builder: (context, state) {
final String minutesStr =
((state.duration / 60) % 60)
.floor()
.toString()
.padLeft(2, '0');
final String secondsStr = (state.duration % 60)
.floor()
.toString()
.padLeft(2, '0');
return Text(
'$minutesStr:$secondsStr',
);
},
),
),
),
BlocBuilder<TimerBloc, TimerState>(
buildWhen: (previousState, currentState) =>
currentState.runtimeType !=
previousState.runtimeType,
builder: (context, state) => TimerActions(),
),
],
),
],
),
),
),
timer.isDeleting
? CircularProgressIndicator()
: IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
onDeletePressed(timer.id);
},
),
],
),
);
}
}
Вот модель таймера, которая передается в блоке списка:
class Timer extends Equatable {
final timer_type timer;
final int duration;
final String id;
final bool isDeleting;
const Timer({
@required this.id,
@required this.timer,
@required this.duration,
this.isDeleting = false,
});
Timer copyWith({timer_type timer, int duration, String id, bool isDeleting}) {
return Timer(
id: id ?? this.id,
duration: duration ?? this.duration,
timer: timer ?? this.timer,
isDeleting: isDeleting ?? this.isDeleting,
);
}
@override
List<Object> get props => [id, duration, timer, isDeleting];
@override
String toString() =>
'Item { id: $id, duration: $duration, timer type: $timer, isDeleting: $isDeleting }';
}
Любая помощь будет с благодарностью принята.
Спасибо!
Ответ №1:
Вы создаете новый UniqueKey
каждый раз, когда список перестраивается, что приводит к удалению состояния
Поэтому, чтобы исправить это, вам нужно связать ключ с таймером, например, обернув таймер в класс, который содержит таймер и ключ, например, так:
class KeyedTimer {
final key = UniqueKey();
Timer timer;
}
Итак, теперь у вас будет список объектов KeyedTimer вместо списка timer, и вы можете использовать ключ в itembuilder списка
Комментарии:
1. Прежде всего, спасибо за ответ! Я обновил сообщение для ясности и включил весь код. Я пытался поместить уникальный ключ в файл ItemTile, и он все равно каждый раз перерисовывается. Нужно ли мне как-то передавать это родительскому или дочернему элементу? Я пробовал включать и отключать ключевое слово super в конструкторе listtile.
2. Я смог сохранить состояние плитки элемента, преобразовав его в виджет с отслеживанием состояния и передав глобальный ключ классу _ItemTileState, но теперь он просто удаляет последний элемент в списке, независимо от того, какой элемент я пытаюсь удалить.
3. Как я уже сказал в ответе, вы должны связать ключ с таймером.
4. Поэтому поместите ключ в свою модель таймера, чтобы он не воссоздавался при каждой сборке. Однако я вижу, что у каждого таймера уже есть уникальный идентификатор, поэтому вместо добавления ключа в класс timer вы можете просто использовать
ValueKey(timer.id
5. Да, я смог исправить эту проблему, но теперь она сбрасывает все таймеры ниже удаленного элемента.