【问题标题】:Flutter BLoC: Maintaining Child State when Parent Widget UpdatesFlutter BLoC:在父 Widget 更新时维护子状态
【发布时间】: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 }';
}

任何帮助将不胜感激。

谢谢!

【问题讨论】:

    标签: flutter bloc


    【解决方案1】:

    每次重建列表时都会创建一个新的UniqueKey,这会强制删除状态

    因此,要修复它,您必须将键与计时器相关联,例如通过将计时器包装在包含计时器和键的类中,如下所示:

    class KeyedTimer {
      final key = UniqueKey();
      Timer timer;
    }
    

    所以现在您将拥有一个 KeyedTimer 对象列表而不是计时器列表,并且您可以在列表的 itembuilder 中使用键

    【讨论】:

    • 首先感谢您的回复!为了清楚起见,我更新了帖子并包含了整个代码。我试图在 ItemTile 文件中放置一个唯一键,但它仍然每次都被重绘。我是否需要以某种方式将其传递给父母或孩子?我尝试在 listtile 构造函数上启用和禁用 super 关键字。
    • 我能够通过将项目磁贴转换为有状态小部件并将全局键传递给 _ItemTileState 类来保持项目磁贴的状态,但现在它只是删除列表中的最后一个元素,无论我要删除哪个元素。
    • 就像我在答案中所说,您必须将密钥与计时器相关联。
    • 因此将密钥放入您的计时器模型中,这样它就不会在每次构建时都重新创建。但是,我看到每个计时器已经有一个唯一的 id,因此您可以使用 ValueKey(timer.id 而不是将密钥添加到计时器类
    • 是的,我能够解决这个问题,但现在它正在重置已删除项目下方的所有计时器。
    猜你喜欢
    • 2021-03-19
    • 2020-10-10
    • 2020-02-27
    • 2021-08-02
    • 2022-08-11
    • 2023-04-09
    • 2020-05-07
    • 1970-01-01
    • 2021-01-06
    相关资源
    最近更新 更多