preface

This is an article I’ve been wanting to write for a long time, and I finally decided to write it.

Anyone who writes about Flutter may have noticed that the topic of “The Hell of fluttering dolls” is always a topic that will never go out of style under the popular Flutter articles of nuggets, the Flutter topics of Zhihu, or some forums.

If you are not convinced, go up and argue: “Nesting is your code habit. Look at me. Raise a Row and reverse a Column, and lift the widget in children. Then you may be sprayed by a group of people into a dog, careless, this post does not have the same camp of friends, spray, but flash; Generally after being sprayed, not the body after being sprayed, all need a period of time to calm down…

So, I finally decided to put this article out, and if you read it carefully, you will probably find that nesting is nothing, your page code will become super maintainable, and the entry of interaction logic will become clear.

Throughout the article, there is nothing to teach you how to do things. This is a kind of behavior that has to be so standardized in the project.

To prepare

To improve the

Let’s talk about what this article can help you improve:

  • Page layer widget crazy nesting doll thousands of lines, late maintenance, mentality collapse and other problems

    • Doll does not divide the page, late demand changes, let you change page details and even structure, it will be a very uncomfortable thing
  • Logical interactive event entries, intermingled with widgets, make it difficult to find problems

    • If you are crazy about nesting on the page layer, you will find it very tiring to find the logical interactive entry even if you use cubit, getx, etc. in the provider, bloc, etc.
    • Then beeping up here, and the framework, the author must be found this kind of situation, so get out of the event layer to bloc, fish_redux up action layer, to unified management and its events entrance.
  • The page structure is so full of detail that it can be difficult to structure

These problems about the page layer above, if the collaboration of many people to develop a large project, the code is not standard, most likely will encounter (change the module written by others…). ; Late change demand, really is a kind of torture, there is a feeling of looking for a needle; If you change your own module, that may be better, after all, you still have a bit of memory, the general idea of the whole module, and know how to change. If you want to change a module written by someone else, you will have to figure out the intention of others to write these widgets in a sea of widgets. The structure will not be clear, and it will be very painful.

Effect of the Demo

In the idea of the article, in the Demo page must not be too simple, a simple Demo page, how can demonstrate the improvement of the doll hell? After thinking for a long time, I wanted to find a suitable demo page. During the weekend, I was listening to the tomb raiding novel in Himalaya. After checking the discovery page, I found that the overall style is good. And the overall page complexity is enough to demonstrate!

Himalayan this PC page Demo, write up really took a lot of time, liver pain.

address

  • Web:Imitation Himalaya page
    • The Web does not enforce window sizing, so you may need to adjust the width of your Web window to get the best results
  • Windows:Windows platform installation package
    • If you have 125% on your computerScaling and LayoutPlease hit the home pageOpen the zoombutton
  • Project address: flutter_use

instructions

The code has been published on Github, and the Web side has been deployed, because the CanvasKit mode is used to package, the first load may be slow, wait a little longer, because the Web side is deployed on Github, to access, make sure that your network can access Github; CanvasKit mode packaged web, on the mobile phone access effect is also good, WE in this is absolutely not and the front end of those awesome awesome frame than, just let yourself more possibilities, can also make some small things

  • Widows installation package
    • Windows laptop high screen tend to open 125% of the scale, at that time, there is a pit than the problem of open scale, the layout of the Flutter of the scaling, but pit than that of the whole Window will not scaling, to accumulate Window of the whole content, this problem I also on several computers, adjustable for a long time to find.
    • Solution, write a manual open adaptation function.
    • aboutOpen the zoomI found that after window_size initialization, after setting the window size for the first time; Then, when setting the window again, the setting is larger, the callback is invalid, strange…

Effect of contrast

Let’s compare the imitation effect, there are 60% or 70% similar, many ICONS and pictures are really not similar, here the demo only provides a style demonstration, forget the function, this is not a day, one person can create…

The photos are all taken from Ximalaya Web terminal, and the data is changing all the time. The data in the corresponding columns are not consistent, but the overall style is roughly the same.

The Banner module is the biggest difference, with a three-party library can only support such, you will have a look.

  • Original Himalayan PC page

  • Imitation Himalaya page

conclusion

The above two groups of pictures, the comparison of details is basically horrible, but the overall structure is still relatively similar.

Due to size limitation, the picture cannot show all the contents. The information flow module on the right still has some information not shown: the latest selection, hot anchors, free list of classics, free list of audio books, free list of crosstalk storytelling.

I suggest you download the Windows installation package and experience the installation. MacOS folks, if you look at the web presentation, you can’t pack without apple and you don’t want to mess with black apples.

Let’s take a look at the specification code in a minute. Complex modules that make your code highly maintainable!

Open to

Analysis of the

  • Android business custom View
    • In Android there is a page module development idea, the whole page is divided into several business custom View, we only need to pay attention to the incoming data source, and the corresponding business View interactive events back information, this is obviously the idea of the appearance mode… Attention: Data source and interaction events are all we care about, and then in the main page, we can combine these business views, completely discard the include pit, include allows XML to be coupled, and if you change an XML that is referenced in multiple places, it might cause some impact. You can think about it in your heart.
  • The Flutter of the Widget
    • The system widgets of Flutter are basically functional widgets that need to define a large number of field values. The advantage of this method is that the required fields can be controlled in a very granular manner. Combined with some defined return functions, Flutter can: The perfect combination of data sources and interactive callbacks.

Combined with the business View and all things Widget thinking above, we can come to a conclusion: Make business widgets, and then put them together!

Of course, we have drawn a conclusion here that is not a conclusion. Generally speaking, this kind of operation is our basic literacy, but there are still a lot of details to pay attention to:

  • Business widgets also require partitioning modules
  • Columns, rows, things like that have natural structures. How do you use them
  • How to encapsulate the details of the widgets on the side

Main module encapsulation

After all the analysis above, one conclusion can be drawn: build a business Widget!

For details about the encapsulation of business widgets, here are some of the details:

  • Try to use only one data source, do not use too many fields to partition
    • To clarify, this is a business widget, not a functional widget. Excessive segmentation field input will cause the widget to be too long. Most business widgets are only used in your module, and other modules are rarely used. The business widgets you encapsulate, 95 percent of the time, will only end up on the page you wrote…
    • If it’s a generic widget, you can subdivide the field or use intermediate entities. General module development, about the data source input, you need to consider some more general data format, such as only need a list data, do not make an entity, only need a field, do not need to make a list and so on…
  • Interaction events must be exposed using callback functions
    • The interaction events must be exposed and handled by the business or logical layer; Generally speaking, the user enters the page, clicks or slides the page, and that is when the business event occurs, which must be exposed, remember.

Imitation Himalayan main module

  • Take a look at the code for the main module of the faux Himalaya PC page

    • A little knowledge of Getx is used here. If you don’t know about Flutter Getx, check out this article: Flutter Getx uses – the charm of simplicity!
    • When assembling the corresponding business Widget: Remember that the corresponding business Widget must be annotated
  • Here is the main module of all the code, we feel conscience say:

    • Is this still dead nesting?
    • Is this still a Russian doll?
    • Does that still look scary?
  • In fact, according to the following encapsulation, basically a combination of View and Event

    • An entry to all business widgets to quickly locate the business widgets that need to be modified
    • All events interact to trigger entry, visible at a glance, can quickly define the corresponding business
class HimalayaPage extends StatelessWidget {
  final HimalayaLogic logic = Get.put(HimalayaLogic());
  final HimalayaState state = Get.find<HimalayaLogic>().state;

  @override
  Widget build(BuildContext context) {
    return himalayaBuildBg(children: [
      // Top: left side navigation bar + right side information flow
      himalayaBuildTopBg(children: [
        // Left navigation bar
        HimalayaLeftNavigation(
          data: state,
          // Navigation bar item callback
          onTap: (Rx<HimalayaSubItemInfo> item) => logic.navigationItem(item),
        ),

        // Right side information flow
        himalayaBuildInfoListBg(children: [
          // The top search box and some personal information Settings buttons
          HimalayaPersonalInfo(
            // Search box input listener
            onChanged: (String msg) => logic.onSearch(msg),
            / / the left arrow
            onLeftArrow: () => logic.dealLeftArrow(),
            / / right arrow
            onRightArrow: () => logic.dealRightArrow(),
            // Refresh button
            onRefresh: () => logic.onRefreshData(),
            // Skin button
            onSkin: () => logic.switchSkin(),
            // Set button
            onSetting: () => logic.onSetting(),
          ),

          // Right side information flow - slippable section
          himalayaBuildScrollInfoListBg(children: [
            / / round figure
            HimalayaBanner(
              data: state.bannerList,
              // Listen for the specific banner
              onTap: (int index) => logic.clickBanner(index),
            ),

            // Guess you like it
            HimalayaGuess(
              data: state.guessList,
              / / in a batch
              onChange: () => logic.guessChange(),
              // Guess which card you like
              onGuess: (HimalayaSubItemInfo item) => logic.guessDetail(item),
            ),

            // The latest selection
            HimalayaNewest(
              data: state,
              // Category title
              onSortTitle: (item) => logic.sortTitle(item),
              // Select specific cards
              onNewest: (HimalayaSubItemInfo item) => logic.onNewest(item),
            ),

            // Hot anchor
            HimalayaAnchor(
              data: state.anchorList,
              onAnchor: (HimalayaSubItemInfo item) => logic.hotAnchor(item),
            ),

            // Various lists
            HimalayaRankList(
              data: state.rankList,
              / / title
              onTitle: (String title) => logic.rankTitle(title),
              // List specific items
              onItem: (HimalayaSubItemInfo item) => logic.rankItem(item),
            ),
          ]),
        ]),
      ]),

      // Bottom: Audio player console
      HimalayaAudioConsole(
        data: state.audioPlayInfo,
        / / the left switch
        onLeftArrow: () => logic.onLeftArrow(),
        / / play
        onPlay: () => logic.onPlay(),
        / / right to switch
        onRightArrow: () => logic.onRightArrow(),
        / / like
        onLove: () => logic.onLove(),
        // Play mode
        onPlayModel: () => logic.onPlayModel(),
        / / cover
        onCover: () => logic.onCover(),
        / / schedule
        onProgress: () => logic.onProgress(),
        / / volume
        onVolume: () => logic.onVolume(),
        / / title
        onSubtitle: () => logic.onSubtitle(),
        / / times the speed
        onSpeed: () => logic.onSpeed(),
        / / timing
        onTiming: () => logic.onTiming(),
        / / directoryonCatalog: () => logic.onCatalog(), ), ]); }}Copy the code

Body detail encapsulation

Generally speaking, a page as a whole is basically a Row or Column structure

The Himalayan module we modeled is also a vertical structure: two large modules

  • Upper module: Navigation bar + information flow => and divided into left and right modules

    • Left module: The side navigation bar on the left => very clear vertical layout
    • Right module: Information flow => This is the simple vertical structure, from top to bottom
  • Next module: Audio player bar => completely horizontal layout

From the above statement, it is clear that the children attribute in Row and Column is the one we care about, and the other details can be wrapped up

Many of the main details of the main module can be wrapped up in a new (module name _function) file:

  • himalaya_function.dart: There are many unnecessary details in the body part, which are all put together in this module. Here, only one needs to be exposedchildrenThe attribute
    • Please do not write these irrelevant details in the main module, it will interfere with the information we need to focus on, these body style is written, it is rarely changed
import 'package:flutter/material.dart';
import 'package:flutter_use/app/base/base_scaffold.dart';
import 'package:flutter_use/app/utils/ui/auto_ui.dart';

///Himalayan overall outer layout setting
Widget himalayaBuildBg({List<Widget> children}) {
  return BaseScaffold(
    backgroundColor: Colors.white,
    body: Column(children: children),
  );
}

///Play the outer layout Settings above the control bar
Widget himalayaBuildTopBg({List<Widget> children}) {
  return Expanded(
    child: Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: children,
    ),
  );
}

///Top right information flow layer layout Settings
Widget himalayaBuildInfoListBg({List<Widget> children}) {
  return Expanded(
    child: Column(children: children),
  );
}

///Top right information flow outer layout Settings - slideable section
Widget himalayaBuildScrollInfoListBg({List<Widget> children}) {
  return Expanded(
    child: Scrollbar(
      child: SingleChildScrollView(
        child: Container(
          width: 860.dp,
          child: Column(children: children),
        ),
      ),
    ),
  );
}
Copy the code

Business Widget encapsulation

The encapsulation of the main module is relatively simple, just encapsulate the details of the main module, expose the children property, and then assemble it

Next comes business Widget encapsulation, which is the core

A few points

  • Try to expose only one data source (non-generic business Widget)
  • All event interactions must be exposed
  • The body details are encapsulated
  • Methods for all widgets in Children

The children in the encapsulation

Let’s take a look at the first case, the most common case, children’s widgets, sorted from top to bottom, with non-list-like data

  • Take a look at the layout of some function buttons at the top. This area involves a lot of event interaction, so it represents a separate business Widget

  • Implementation code: With regard to business widgets, this is the cornerstone, and once the specification is written, it is surprisingly easy to modify later
    • Combining the above illustration with the following code, you should know which widget method corresponds to which control on the interface at a glance. If you want to change a control style, just click on the widget method to change it
    • Make sure to comment every widget method in children, because this is the main entry point for the business widget. It doesn’t matter whether or not you comment the specific widget method
///Search box personal information Settings and other buttons
class HimalayaPersonalInfo extends StatelessWidget {
  HimalayaPersonalInfo({
    Key key,
    this.onRefresh,
    this.onLeftArrow,
    this.onRightArrow,
    this.onSetting,
    this.onSkin,
    this.onChanged,
  }) : super(key: key); .@override
  Widget build(BuildContext context) {
    return _buildBg(children: [
      / / the left icon
      _buildLeftArrow(),

      / / on the right
      _buildRightArrow(),

      // Refresh the icon
      _buildRefresh(),

      / / search box
      _buildSearch(),

      / / avatar
      _buildHeadImg(),

      / / the skin
      _buildSkin(),

      / / set_buildSetting(), ]); }... }Copy the code
  • So let’s take a look at that_buildBgmethods
    • You can find_buildBgThese details of the main body are really irrelevant code. After this is written, basically, the back is rarely changed, so it is extracted and put in the corner to eat dust
///Search box personal information Settings and other buttons
class HimalayaPersonalInfo extends StatelessWidget {... Widget _buildBg({List<Widget> children}) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: 10.dp, horizontal: 18.dp),
      width: 800.dp, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: children, ), ); }}Copy the code
  • About Method Extraction
    • Select the Widget code that you want to extract
    • Open the Flutter Outline selectionRight arrowThe picture

  • After filling in the above method name, a widget method is automatically generated
  • If you extract a Widget block that also contains some data, the auto-generated method will take parameters, which is very convenient

List class style encapsulation

Class list style encapsulation is also more critical, direct from scratch type extraction is not, here is a bit of adjustment

Here’s an example of how you like modules

  • Guess you like modules

  • Code analysis: The overall layout is Column, divided into upper and lower modules
    • Use Row for the upper module
    • The next module is four cards, here is directly used to write the List data source
///Guess you like
class HimalayaGuess extends StatelessWidget {
  HimalayaGuess({
    Key key,
    this.onChange,
    this.data,
    this.onGuess,
  }) : super(key: key); .@override
  Widget build(BuildContext context) {
    return _buildBg(children: [
      // Title + change a batch
      Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
        / / title
        _buildTitle(),

        / / in a batch
        _buildGuessChange()
      ]),

      // Displays the specific information flow
      _buildItemBg(itemBuilder: (item) {
        return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
          // Picture card
          _buildPicCard(item),

          // Text description
          Text(item.title, style: TextStyle(fontSize: 15.sp)),

          / / the author
          Text(item.subTitle, style: TextStyle(fontSize: 13.sp, color: Colors.grey)), ]); })]); }... }Copy the code
  • The children code above is still relatively clear on the whole, a little confusing, probably_buildItemBgTake a look at the code
    • This method exposes one oppositeitemBuilderParameter, which is actually a callback method
    • Because of the list-like style, the entire list data must be traversed, and the specific data that the list traversed needs to be passed back to the Widget, you must use this type of callback method
///Guess you like
typedef HimalayaSubBuilder = Widget Function(HimalayaSubItemInfo item);
class HimalayaGuess extends StatelessWidget {... Widget _buildItemBg({HimalayaSubBuilder itemBuilder}) {return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: data.map((e) {
        returnitemBuilder(e); }).toList(), ); }}Copy the code

What about the encapsulation of a two-tier List data source (each concrete source of a List contains a List)?

  • Two layers of List data source encapsulation is a bit of a hassle. Here, use the sidebar as an example
    • The entire layout is a Column: title + Column.
    • Column: can be divided into specific items
      • Item: Title + column (List data control)

  • Code implementation
    • The overall layout above is driven by the data source page, data can control page item generation
///Data source: Side navigation column initial data, simplified, data source is too long
///The data sources are maintained in the State layer, and are placed here to give you a comparison
leftItemList = [
    HimalayaItemInfo(title: 'recommendations', subItemList: [
        HimalayaSubItemInfo(
            title: 'found',
            icon: CupertinoIcons.compass,
            tag: TagHimalayaConfig.find,
            isSelected: true,),... ] ), HimalayaItemInfo(title:'I', subItemList: [
        HimalayaSubItemInfo(
            title: 'My Subscription', icon: Icons.star_border, tag: TagHimalayaConfig.subscription, ), ......... ] ), HimalayaItemInfo(title:'A playlist I created', subItemList: [
        HimalayaSubItemInfo(
            title: 'The sound I like', icon: Icons.favorite_border, tag: TagHimalayaConfig.sound, ), ............ ] )];///Left navigation bar
class HimalayaLeftNavigation extends StatelessWidget {
  HimalayaLeftNavigation({
    Key key,
    this.data,
    this.onTap,
  }) : super(key: key); .@override
  Widget build(BuildContext context) {
    return _buildBg(children: [
      // Himalayas logo icon
      _buildLogo(),

      // Loop through two layers: different item columns - click, slide
      // Level 1: title + subitem list
      // Layer 2: subitem detailed layout
      _buildItemListBg(itemBuilder: (item) {
        return [
          // The outermost item - the title
          _buildTitle(item.title),

          // Subcolumn - list
          _buildSubItemListBg(
            data: item,
            subBuilder: (subItem) => _buildSubItemBg(data: subItem, children: [
              // Select the red rectangle
              _buildRedTag(subItem),

              / / icon
              _buildItemIcon(subItem),

              / / description_buildItemDesc(subItem), ]), ), ]; })); }... }Copy the code
  • Level 1: Look at the first level_buildItemListBgmethods
    • There are so many properties (scroll, scrollbar, etc.) that it would be a cancer if it were not presented to children
typedef HimalayaItemBuilder = List<Widget> Function(HimalayaItemInfo item);
class HimalayaLeftNavigation extends StatelessWidget {... Widget _buildItemListBg({HimalayaItemBuilder itemBuilder}) {return Expanded(
      child: Scrollbar(
        child: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: data.leftItemList.map((e) {
              returnColumn( crossAxisAlignment: CrossAxisAlignment.start, children: itemBuilder(e), ); }).toList(), ), ), ), ); }}Copy the code
  • The second floor
    • There must be a concrete data source for layer 1 traversal, so you must add an input parameter
    • Here is the general extraction, the only thing you need to be aware of is the data source that is passed in
typedef HimalayaRxSubBuilder = Widget Function(Rx<HimalayaSubItemInfo> item);
class HimalayaLeftNavigation extends StatelessWidget {... Widget _buildSubItemListBg({HimalayaItemInfo data, HimalayaRxSubBuilder subBuilder}){return Column(
            children: data.subItemList.map((e) {
                returnsubBuilder(e); }).toList(), ); }}Copy the code

conclusion

After the above operation, the business Widget becomes N times cleaner immediately

When you write Flutter, you should feel that there is a high degree of freedom to write the page. The style, structure and logic of the page can all be coupled together. So in actual development, pay more attention to your code specification.

Consider a situation: You finish developing a module, after a few months, the demand adjustment, are you going to change the module, see thousands of lines of dolls page code, and then change while swearing, which is then spray the wise people, finally open files git annotations (annotate) records, if it is full of your name, it is not very embarrassed…

digression

A little digression

In fact, writing HTML is also infinite nesting Eva, the difference is that it fundamentally achieves the separation of style structure, control details, all handed over to CSS to do, so the page as a whole looks full of fresh:

  • But there is one thing that makes me feel like a cone. When writing a small program, I need to look for the description style of a specific control across files
    • Uniapp directly put these things in a file (it was like this when I wrote it in 19, I don’t know if it has changed now), which is an improvement and easy to find, but the amount of code in a single file is a bit explosive
  • Because styles are handled by CSS and hierarchy descriptions are placed in CSS, it can sometimes be confusing to look at the code.

The structure of Flutter is not separated from the root structure, and the structure is directly from top to bottom

  • Advantages: Easy to modify the style (convenient positioning); Clear structure (just look down from the top)
  • The bad: Code reading, explosion of perception; Do not do module division, late code maintenance is difficult

So, where there is a perfect frame, there is always a sacrifice…

The development of new things is bound to meet the corresponding resistance

Here’s a hypothetical scenario:

  • You’ve been writing about Flutter for two years, various controls, frames playing with the flying of cows

  • Next thing you know, there’s an amazing, generational front-end framework that seamlessly calls the underlying hardware apis of all platforms, omg, whatever the 6’s

  • And then you see articles about this cross-generational framework popping up all over the tech forums

  • At this moment, will you have something strange in your heart, thinking: Miscellaneous people, Flutter has been written in vain these years? Have to learn the new framework again? Isn’t it cute when I step on a horse?! I’m gonna be in the huddle with a big guy’s leg again!

  • And then you see all those hot articles with all those glowing comments.

  • At this point, there will be silk waves in your heart, want to be a sober person when this technology group, can’t help chanting: everyone is drunk I wake up alone…..

  • Then, picked up the keyboard, incarnation of a big troll, with a hundred enemies, not falling wind

  • Instant, let you feel: this forum, now called LBW forum! I am the king of this forum!

Role reversal

In fact, we don’t need to pay attention to a lot of comments; The roles are reversed, and the other person’s behavior at this moment is what we might do ourselves later.

In fact, we are all workers, why tear to tear?

The last

DEMO address: flutter_use

series

After the above code specification operation, and with GetX state management, I believe that the general project, you can hold

Come on, we’re the best on the street

  • State Management: Use Flutter GetX – Simple charm!

  • A more elegant solution to the Flutter Dialog