Flutter BLoC:在父 Widget 更新时维护子状态
Posted
技术标签:
【中文标题】Flutter BLoC:在父 Widget 更新时维护子状态【英文标题】:Flutter BLoC: Maintaining Child State when Parent Widget Updates 【发布时间】:2021-02-11 02:31:13 【问题描述】:我现在有一个应用程序可以创建计时器列表。它使用两个 BLoC,一个用于单个计时器,一个用于整个列表。您可以查看以下列表的示例:
在图像中,您可以看到索引 0 和 2 尚未启动,而索引 1 已启动。我的问题是,每当我从列表中添加或删除项目时,所有计时器都会重置。
当父窗口部件被重绘时,有没有办法让它们的状态保持不变?
这是列表代码:
这里的 Item Tile 包含计时器的 BLoC:
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);
,
),
],
),
);
这是在列表 BLoC 中传递的计时器模型:
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 对象列表而不是计时器列表,并且您可以在列表的 itembuilder 中使用键
【讨论】:
首先感谢您的回复!为了清楚起见,我更新了帖子并包含了整个代码。我试图在 ItemTile 文件中放置一个唯一键,但它仍然每次都被重绘。我是否需要以某种方式将其传递给父母或孩子?我尝试在 listtile 构造函数上启用和禁用 super 关键字。 我能够通过将项目磁贴转换为有状态小部件并将全局键传递给 _ItemTileState 类来保持项目磁贴的状态,但现在它只是删除列表中的最后一个元素,无论我要删除哪个元素。 就像我在答案中所说,您必须将密钥与计时器相关联。 因此将密钥放入您的计时器模型中,这样它就不会在每次构建时都重新创建。但是,我看到每个计时器已经有一个唯一的 id,因此您可以使用ValueKey(timer.id
而不是将密钥添加到计时器类
是的,我能够解决这个问题,但现在它正在重置已删除项目下方的所有计时器。以上是关于Flutter BLoC:在父 Widget 更新时维护子状态的主要内容,如果未能解决你的问题,请参考以下文章
flutter_bloc 的 BlocBuilder 能否避免重建 Widget 的未更改部分?
Flutter Bloc - Flutter Bloc 状态未更新
Flutter BloC 模式:基于另一个 BloC 的流更新 BloC 流