首页 > 解决方案 > 无状态小部件中的非最终字段

问题描述

首先,我需要说,我不确定 stackoverflow 是否适合问这个问题,但除了社区之外,我没有人可以问,因为我们在小型初创公司工作,除了我们两个之外,没有 Flutter 开发人员。

我(大约一年开发,大约半年时间学习flutter)和一个有点“10年经验的资深移动开发者”的激烈讨论。

讨论的主题是在无状态小部件中使用非最终字段。他正在这样做,他正在编写这样的代码。他说这是解决他的问题的最好方法。我说这是个坏主意,要么他需要有状态的小部件,要么他的设计很糟糕,他不需要非最终字段。

所以我的问题是:是否存在在无状态小部件中使用非最终字段是合理的情况?

他的论点:

  1. 我们使用的是 BLoC 模式,因为在 StatelessWidget 我们有 BlocBuilder,这个 StatelessWidget 有状态。
  2. 愚蠢的 Dart linter 不知道我们的“BLoC 情况”
  3. 如果我们使用有状态的小部件,代码的可读性会变差。
  4. 如果我们将使用有状态小部件,我们将获得额外的开销。

我知道前两个论点很愚蠢,只有第四个论点值得讨论。

这个问题的可能重复也不能说服我的同事。 Flutter:无状态小部件中的可变字段

请看他的代码:

class GameDiscussThePicture extends StatelessWidget {

  GameDiscussThePicture();

  CarouselSlider _slider;

  @override
  Widget build(BuildContext context) {
    return BlocBuilder(
      bloc: BlocProvider.of<ChatBloc>(context),
      condition: (previousState, state) {
        return previousState != GameClosed();
      },
      builder: (context, state) {
        if (state is GameDiscussTopicChanged) {
          _showPictureWith(context, state.themeIndex);
        } else if (state is GameClosed) {
          Navigator.of(context).pop();
          return Container();
        }
      final _chatBloc = BlocProvider.of<ChatBloc>(context);
      return Scaffold(
        appBar: AppBar(
          backgroundColor: Color.fromARGB(255, 255, 255, 255),
          leading: BackButton(
            color: Color.fromARGB(255, 12, 12, 13),
            onPressed: () => BlocProvider.of<ChatBloc>(context).add(GameCancel()),
          ),
        ),
        //SafeArea
        body: DecoratedBox(
          decoration: BoxDecoration(color: Color.fromARGB(255, 240, 240, 240)),
          child: Row(
            mainAxisSize: MainAxisSize.max,
            mainAxisAlignment: MainAxisAlignment.start,
            children: [
              Expanded(
                child: Column(
                  mainAxisSize: MainAxisSize.max,
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    SizedBox(height: 15),
                    _carouselSlider(context),
                    Container(
                      height: 88,
                      child: DecoratedBox(
                        decoration: BoxDecoration(color: Color.fromARGB(255, 255, 255, 255)),
                        child: Row(
                          mainAxisSize: MainAxisSize.max,
                          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                          children: [
                            if (_chatBloc.partnerAvatar() != null) Image.network(_chatBloc.partnerAvatar(), fit: BoxFit.cover, width: 75.0),
                            if (_chatBloc.partnerAvatar() == null) Text('RU', style: TextStyle(fontSize: 22)),
                          Padding(
                            padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
                            child: Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(_chatBloc.partnerName(), style: TextStyle(fontSize: 20, fontWeight: FontWeight.normal),),
                                ChatStopwatch(),
                                // Text('До конца 06:33', style: TextStyle(fontSize: 14, fontWeight: FontWeight.normal),),
                              ],
                            )
                          ),
                          // FlatButton(
                          //   child: Image.asset('assets/images/mic_off.png', width: 30, height: 30,),
                          //   onPressed: () => print('mic off pressed'),
                          // ),
                          FlatButton(
                            child: Image.asset('assets/images/hang_off.png', width: 60, height: 60,),
                            onPressed: () => ChatHelper.confirmEndingDialog(context)
                          ),
                        ]),
                    ))
                  ],
                ),
              ),
            ],
          ),
        ),
      );
    });
  }

  @widget
  Widget _carouselSlider(BuildContext context) {    
    final chatBloc = BlocProvider.of<ChatBloc>(context);
    _slider = CarouselSlider(
      height: 600.0,
      viewportFraction: 0.9,
      reverse: false,
      enableInfiniteScroll: false,
      initialPage: chatBloc.gameDiscussCurrentIdx,
      onPageChanged: (index) {
        final chatBloc = BlocProvider.of<ChatBloc>(context);
        if (chatBloc.gameDiscussCurrentIdx < index) {
          chatBloc.add(GameDiscussTopicChange(themeIndex: index));
        } else {
          _slider.animateToPage(chatBloc.gameDiscussCurrentIdx, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
        }
      },
      items: chatBloc.gameDiscussPictures.map((item) {
        return Builder(
          builder: (BuildContext context) {
            return Container(
              width: MediaQuery.of(context).size.width,
              margin: EdgeInsets.symmetric(horizontal: 5.0),
              child: Column(
                mainAxisSize: MainAxisSize.max,
                mainAxisAlignment: MainAxisAlignment.start,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(item.titleEn, style: Styles.h3),
                  SizedBox(height: 15.0,),
                  ClipRRect(
                    borderRadius: BorderRadius.all(Radius.circular(15.0)),
                    child: Image.network(item.getImageUrl(), fit: BoxFit.cover, width: MediaQuery.of(context).size.width),
                  )
                ]
              ),
            );
          },
        );
      }).toList(),
    );
    return _slider;
  }

  _onPictureChanged(BuildContext context, int index) {
    final chatBloc = BlocProvider.of<ChatBloc>(context);
    if (chatBloc.gameDiscussCurrentIdx < index) {
      chatBloc.add(GameDiscussTopicChange(themeIndex: index));
    } else {
      _slider.animateToPage(chatBloc.gameDiscussCurrentIdx, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
    }
  }

  _showPictureWith(BuildContext context, int index) {
      final chatBloc = BlocProvider.of<ChatBloc>(context);
      chatBloc.gameDiscussCurrentIdx = index;
      _slider.animateToPage(chatBloc.gameDiscussCurrentIdx, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
  }
}

标签: flutterblocflutter-bloc

解决方案


免责声明:我不擅长解释,希望您阅读这个垃圾解释有所收获。我什至不认为这可以称为解释

class GameDiscussThePicture extends StatelessWidget {

  GameDiscussThePicture();

  /// As he said, BLoC is the one holding state, therefore if he wants a non final field 
  /// declare it in ChatBloc not here.
  /// class ChatBloc extends Bloc {
  ///   CarouselSlider _slider;
  ///   CarouselSlider get slider => _slider;
  /// }
  /// To access it, BlocProvider.of<ChatBloc>(context).slider;
  CarouselSlider _slider;

  @override
  Widget build(BuildContext context) {
    return BlocBuilder(
      bloc: BlocProvider.of<ChatBloc>(context),
      condition: (previousState, state) {
        return previousState != GameClosed();
      },
      builder: (context, state) {
        if (state is GameDiscussTopicChanged) {
          _showPictureWith(context, state.themeIndex);
        } else if (state is GameClosed) {
          Navigator.of(context).pop();
          return Container();
        }
      final _chatBloc = BlocProvider.of<ChatBloc>(context);
      return Scaffold(
        appBar: AppBar(
          backgroundColor: Color.fromARGB(255, 255, 255, 255),
          leading: BackButton(
            color: Color.fromARGB(255, 12, 12, 13),
            /// he can just write _chatBloc.add(GameCancel()) here.
            onPressed: () => BlocProvider.of<ChatBloc>(context).add(GameCancel()),
          ),
        ),
        //SafeArea
        body: DecoratedBox(
          decoration: BoxDecoration(color: Color.fromARGB(255, 240, 240, 240)),
          child: Row(
            mainAxisSize: MainAxisSize.max,
            mainAxisAlignment: MainAxisAlignment.start,
            children: [
              Expanded(
                child: Column(
                  mainAxisSize: MainAxisSize.max,
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    SizedBox(height: 15),
                    _carouselSlider(context),
                    Container(
                      height: 88,
                      child: DecoratedBox(
                        decoration: BoxDecoration(color: Color.fromARGB(255, 255, 255, 255)),
                        child: Row(
                          mainAxisSize: MainAxisSize.max,
                          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                          children: [
                            if (_chatBloc.partnerAvatar() != null) Image.network(_chatBloc.partnerAvatar(), fit: BoxFit.cover, width: 75.0),
                            if (_chatBloc.partnerAvatar() == null) Text('RU', style: TextStyle(fontSize: 22)),
                          Padding(
                            padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
                            child: Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(_chatBloc.partnerName(), style: TextStyle(fontSize: 20, fontWeight: FontWeight.normal),),
                                ChatStopwatch(),
                                // Text('До конца 06:33', style: TextStyle(fontSize: 14, fontWeight: FontWeight.normal),),
                              ],
                            )
                          ),
                          // FlatButton(
                          //   child: Image.asset('assets/images/mic_off.png', width: 30, height: 30,),
                          //   onPressed: () => print('mic off pressed'),
                          // ),
                          FlatButton(
                            child: Image.asset('assets/images/hang_off.png', width: 60, height: 60,),
                            onPressed: () => ChatHelper.confirmEndingDialog(context)
                          ),
                        ]),
                    ))
                  ],
                ),
              ),
            ],
          ),
        ),
      );
    });
  }

  @widget
  /// Also rather than passing BuildContext, passing the _chatBloc is better.
  /// I am not sure why, but I've read somewhere BuildContext is not meant to be passed
  /// around. And you don't need to make another final field for BlocProvider.of<ChatBloc> 
  /// (context)
  /// Widget _carouselSlider(ChatBloc chatBloc) {
  ///   and here you can do something like chatBloc.slider = CarouselSlider(); in case
  ///   that slider field will be used again somehow.
  /// }
  /// Tho just return CarouselSlider instead is better in this scenario IMO.
  Widget _carouselSlider(BuildContext context) {    
    final chatBloc = BlocProvider.of<ChatBloc>(context);
    _slider = CarouselSlider(
      height: 600.0,
      viewportFraction: 0.9,
      reverse: false,
      enableInfiniteScroll: false,
      initialPage: chatBloc.gameDiscussCurrentIdx,
      onPageChanged: (index) {
        final chatBloc = BlocProvider.of<ChatBloc>(context);
        if (chatBloc.gameDiscussCurrentIdx < index) {
          chatBloc.add(GameDiscussTopicChange(themeIndex: index));
        } else {
          _slider.animateToPage(chatBloc.gameDiscussCurrentIdx, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
        }
      },
      items: chatBloc.gameDiscussPictures.map((item) {
        return Builder(
          builder: (BuildContext context) {
            return Container(
              width: MediaQuery.of(context).size.width,
              margin: EdgeInsets.symmetric(horizontal: 5.0),
              child: Column(
                mainAxisSize: MainAxisSize.max,
                mainAxisAlignment: MainAxisAlignment.start,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(item.titleEn, style: Styles.h3),
                  SizedBox(height: 15.0,),
                  ClipRRect(
                    borderRadius: BorderRadius.all(Radius.circular(15.0)),
                    child: Image.network(item.getImageUrl(), fit: BoxFit.cover, width: MediaQuery.of(context).size.width),
                  )
                ]
              ),
            );
          },
        );
      }).toList(),
    );
    return _slider;
  }

  _onPictureChanged(BuildContext context, int index) {
    final chatBloc = BlocProvider.of<ChatBloc>(context);
    if (chatBloc.gameDiscussCurrentIdx < index) {
      chatBloc.add(GameDiscussTopicChange(themeIndex: index));
    } else {
      _slider.animateToPage(chatBloc.gameDiscussCurrentIdx, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
    }
  }

  _showPictureWith(BuildContext context, int index) {
      final chatBloc = BlocProvider.of<ChatBloc>(context);
      chatBloc.gameDiscussCurrentIdx = index;
      _slider.animateToPage(chatBloc.gameDiscussCurrentIdx, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
  }
}

推荐阅读