无状态小组件中的非最终字段

时间:2020-02-05 02:48:11

标签: flutter bloc flutter-bloc

首先,我要说的是,我不确定是否应该在stackoverflow上问这个问题,但是除了社区,我没有人要问这个问题,因为我们是在小型创业公司中工作,除了两个人之外,其他开发人员都没有动静我们。

我(大约一年的开发时间,大约一半的时间研究flutter)和一种“具有10年经验的高级移动开发人员”进行了激烈的讨论。

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

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

他的论点:

  1. 我们正在使用BLoC模式,因为在StatelessWidget中我们具有BlocBuilder,所以此StatelessWidget具有状态。
  2. 愚蠢的达特linter不知道我们的“ BLoC情况”
  3. 如果我们将使用有状态的小部件,则代码的可读性会变差。
  4. 如果我们将使用有状态的小部件,则会增加额外的开销。

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

这个问题的可能重复也不能说服我的同事。 Flutter: Mutable fields in stateless widgets

请查看他的代码:

namespace PizzaCreator
{
    class Program
    {
        static void Main (string[] args)
        {
            var done = false;
            do
            {
                switch (DisplayMenu())
                {
                    case Command.New: NewOrder(); break;
                    case Command.Display: DisplayOrder(); break;
                    case Command.Quit: done = true; break;
                }
            } while (!done);
        }

        enum Command
        {
            Quit = 0,
            Display = 1,
            New = 2,
        }

        static string size;
        static string meat;
        static string vegtables;
        static string sauce;
        static bool cheese;
        static bool delivery;

        private static Command DisplayMenu ()
        {
            do
            {
                Console.WriteLine("N)ew Order: ");
                Console.WriteLine("D)isplay Order: ");
                Console.WriteLine("Q)uit.");

                var input = Console.ReadLine();
                switch (input.ToLower())
                {
                    case "n": return Command.New;
                    case "d": return Command.Display;
                    case "q": return Command.Quit;

                    default: Console.WriteLine("Invalid option, please enter a valid letter. "); break;
                };
            } while (true);
        }

        private static bool ReadBoolean (string message)
        {
            Console.WriteLine(message + " (Y/N) ");
            do
            {
                string value = Console.ReadLine();
                if (!String.IsNullOrEmpty(value))
                {
                    if (String.Compare(value, "Y", true) == 0)
                        return true;
                    else if (String.Compare(value, "N", true) == 0)
                        return false;
                    char firstChar = value[0];
                };
                Console.WriteLine("Enter Y/N: ");
            } while (true);
        }

        static void NewOrder()
        { 
            Console.WriteLine(Size());
            Console.WriteLine(Sauce());
            Console.WriteLine(Cheese());
            Console.WriteLine("Meat: ");
            Console.WriteLine("Vegetables: ");
            Console.WriteLine("Delivery: (choose one)");
        }

        private static bool Size ()
        {
            Console.WriteLine("Pizza size:\n S)mall(5.00)\n M)eduium(6.25)\n L)arge(8.75)");
            Console.Write("Choose one: ");
            var choice = Console.ReadLine();
            double cost = 0;

            switch (choice)
            {
                case "S":
                 cost += 5.00;
                 break;

                 case "M":
                 cost += 6.25;
                 break;

                 case "L":
                 cost += 8.75;
                 break;

                 default:
                 Console.WriteLine("Invalid Selection please select S, M, or L");
                 break;
            }
            return (true);
        }

        private static bool Sauce ()
        {
            Console.WriteLine("Type of Sauce:\n T)raditional(0.00)\n G)arlic(1.00)\n O)regano(1.00)");
            Console.Write("Choose one: ");
            var choice = Console.ReadLine();
            double cost = 0;

            switch (choice)
            {
                case "T":
                cost += 0.00;
                break;

                case "G":
                cost += 1.00;
                break;

                case "O":
                cost += 1.00;
                break;

                default:
                Console.WriteLine("Invalid Selection please select T, G, or O");
                break;
            }
            return true;
        }

        private static bool Cheese()
        {
            double cost = 0;
            Console.WriteLine("R)eagular($0.00)\nE)xtra($1.25) ");
            do
            {
                string value = Console.ReadLine();
                if (!String.IsNullOrEmpty(value))
                {
                    if (String.Compare(value, "R", true) == 0)
                        return true;
                    else if (String.Compare(value, "E", true) == 0)
                        cost += 1.25;
                        return false;
                    char firstChar = value[0];
                };
                Console.WriteLine("Enter R/E ");
            } while (true);
        }

        static void Meat()
        {

        }

        static void Vegtables()
        {

        }

        static bool Delivery()
        {
            double cost = 0;
            Console.WriteLine("T)akeout($0.00)\nD)elivery($1.25) ");
            do
            {
                string value = Console.ReadLine();
                if (!String.IsNullOrEmpty(value))
                {
                    if (String.Compare(value, "T", true) == 0)
                        return true;
                    else if (String.Compare(value, "D", true) == 0)
                        cost += 2.50;
                        return false;
                    char firstChar = value[0];
                };
                Console.WriteLine("Enter T/D ");
            } while (true);
        }

        private static void DisplayOrder()
        {
            if (String.IsNullOrEmpty(size))
            {
                Console.WriteLine("No Pizza Selected");
                return;
            }

            Console.WriteLine(size);

            Console.WriteLine(sauce);

            Console.WriteLine(cheese ? "Cheese" : "No cheese");

            Console.WriteLine(meat);

            Console.WriteLine(vegtables);

            Console.WriteLine(delivery ? "Delivery" : "Take out");

        }
    }
}

1 个答案:

答案 0 :(得分:1)

免责声明:我不擅长解释,希望您能从阅读此垃圾内容中得到一些帮助。我什至不认为这可以称为解释

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);
  }
}