background

The deep level of nesting is a problem that disturbs many people who are new to Flutter. Not only is it uncomfortable to look at, but it also affects the coding experience.

The big guns will tell you that you should break down your nested code (custom widgets or extract build methods) to reduce the level of nesting. This is indeed an effective method, and if there is any other way, this article will show you another way to reduce the nesting hierarchy.

Too much nesting affects the look and feel of the code

This code demonstrates what is called: nested hell

class Test extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Demo'),),
      body: Container(
        child: Offstage(
          offstage: false,
          child: ListView(
            children: <Widget>[
              Container(
                color: Colors.white,
                padding: EdgeInsets.all(20),
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[
                    Icon(Icons.phone),
                    Text("amy"),
                  ],
                ),
              ),
              Container(
                color: Colors.white,
                padding: EdgeInsets.all(20),
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[
                    Icon(Icons.phone),
                    Text("billy""" "" "" "" "" "" "" }}Copy the code

With the build method extracted, the nesting hierarchy is significantly improved

class Test extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Demo'),),
      body: Container(
        child: Offstage(
          offstage: false,
          child: ListView(
            children: <Widget>[
              buildItem("amy"),
              buildItem("billy""" "" "" "" "" " } Container buildItem(String name) {
    return Container(
      color: Colors.white,
      padding: EdgeInsets.all(20), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Icon(Icons.phone), Text(name), ], ), ); }}Copy the code

Can we continue to optimize?

Custom extension functions

For example: I want to add marginTop:10 to the second Textwidget in the code below

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(10),
      child: Column(
        children: <Widget>[
          Text('billy'),
          Text('say hello'), //add margin top??],),); }Copy the code

At this point, I wish I could write this:

Obviously, the Flutter does not support this writing, but fortunately: Dart2.7 officially announced support for Extension Methods when it was released.

In fact, the dart extension function has been supported since dart 2.6.0. If the dart version set in pubspec.yaml is later than 2.6.0, an alert will appear, such as: Environment: SDK:"> = 2.1.0 < 3.0.0"Warning: Extension methods weren't supported until version 2.6.0Copy the code

Let’s define an extension function

extension WidgetExt on Widget {

  Container intoContainer({
  	// Copy all arguments to the Container constructor (except the child field)
    Key key,
    AlignmentGeometry alignment,
    EdgeInsetsGeometry padding,
    Color color,
    Decoration decoration,
    Decoration foregroundDecoration,
    double width,
    double height,
    BoxConstraints constraints,
    EdgeInsetsGeometry margin,
    Matrix4 transform,
  }) {
  	// Call the Container constructor with the current Widget object as the child parameter
    return Container(
      key: key,
      alignment: alignment,
      padding: padding,
      color: color,
      decoration: decoration,
      foregroundDecoration: foregroundDecoration,
      width: width,
      height: height,
      constraints: constraints,
      margin: margin,
      transform: transform,
      child: this,); }}Copy the code

All widget objects now have an intoContainer(…) The Container constructor takes the same parameters as the Container constructor, so we can write:

Containers other than Containers can be extended in the same way. As a result, the programming experience is greatly improved.

Chained calls can also be supported:

Text("billy")
  .intoExpanded(flex: 1)
  .intoContainer(color: Colors.white)
Copy the code

Some widgets have more than one child. You can add the following extension functions:

extension WidgetExt on Widget {
  // Add an adjacent widget, return List< widget >
  List<Widget> addNeighbor(Widget widget) {
    return <Widget>[this, widget];
  }

  // Add a widget container with a single child
  // Container, Padding, etc...
}

extension WidgetListExt<T extends Widget> on List<T> {
  // Add another adjacent Widget to the sublist 
      
        and return the current List
      
  List<Widget> addNeighbor(Widget widget) {
    return this. add(widget); } Row intoRow({ Key key, MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, MainAxisSize mainAxisSize = MainAxisSize.max, CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, TextDirection textDirection, VerticalDirection verticalDirection = VerticalDirection.down, TextBaseline textBaseline, {})return Row(
      key: key,
      mainAxisAlignment: mainAxisAlignment,
      mainAxisSize: mainAxisSize,
      crossAxisAlignment: crossAxisAlignment,
      textDirection: textDirection,
      verticalDirection: verticalDirection,
      textBaseline: textBaseline,
      children: this,); }// Add another widget container with multiple children
  // Column, ListView, etc...
}
Copy the code

Use extension functions to solve the problem of too deep nesting

Going back to the original nesting hell of this article, our code can now be written like this

class Test extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('Demo'),),
        body: buildItem("amy")
              .addNeighbor(buildItem("billy"),)
              .intoListView()
              .intoOffstage(offstage: false)
              .intoContainer()
    );
  }

  Container buildItem(String name) {
    return Icon(Icons.phone)
        .addNeighbor(Text(name))
        .intoRow(crossAxisAlignment: CrossAxisAlignment.center,)
        .intoContainer(color: Colors.white, padding: EdgeInsets.all(20)); }}Copy the code

To make our code more chaining, let’s define another static method

class WidgetChain {
  static Widget addNeighbor(Widget widget) {
    returnwidget; }}Copy the code

In addition, define an extension of the mapping from the data to the widget

extension ListExt<T> on List<T> {

  List<Widget> buildAllAsWidget(Widget Function(T) builder) {
    return this.map<Widget>((item) {
      returnbuilder(item); }).toList(); }}Copy the code

Now, the code looks like this:

class Test extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('Demo'),),
        body: ["amy"."billy"]
            .buildAllAsWidget((name) =>
              WidgetChain
              .addNeighbor(Icon(Icons.phone))
              .addNeighbor(Text(name))
              .intoRow(crossAxisAlignment: CrossAxisAlignment.center,)
              .intoContainer(color: Colors.white, padding: EdgeInsets.all(20),)
            )
            .intoListView()
            .intoOffstage(offstage: false) .intoContainer() ); }}Copy the code

It is worth noting that extension functions (which are not nested) and constructors (which are nested) can be mixed. The code above can also be written like this (Container and Offstage are replaced with constructors) :

class Test extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Demo'),),
      body: Container(
        child: Offstage(
          offstage: false,
          child: ["amy"."billy"]
            .buildAllAsWidget((name) =>
              WidgetChain
              .addNeighbor(Icon(Icons.phone))
              .addNeighbor(Text(name))
              .intoRow(crossAxisAlignment: CrossAxisAlignment.center,)
              .intoContainer(color: Colors.white, padding: EdgeInsets.all(20),) ) .intoListView() ), ), ); }}Copy the code

Do you want to try this extension function? I’ve packaged the into extension function for common widgets for you to eat:

dependencies:
  widget_chain: ^ 0.1.0 from
Copy the code

Import:

import 'package:widget_chain/widget_chain.dart';
Copy the code

Then we can take off!

If someone gives you a laugh flutter is too deep, you can throw widget_chain to them 😀

conclusion

This article introduces the nested hell in Flutter and uses the extension function to solve the nested hell problem in Flutter.

Due to the expansion of large space function call can affect the reading experience of code (because of intoXxx function call order and widget hierarchy are opposite), needs to retain key nested hierarchical structure to make the layout of the hierarchy to keep clear, in this paper, the extension function of support and the mixture of constructor, the specific use of to what extent, depends on how much you own choice

Additional instructions after editing of 2019-12-30

This article was originally published with information on how to use extension functions to improve the coding experience. Because the way I added the parent container when I was writing my Flutter code was:

  • Cut the widget code to add the parent container
  • Write the parent container and its properties at the cut
  • Add the child under the parent container node and paste the content you just cut as the value of the child

Later, in the Wechat group (Gityuan Flutter technology communication group), the big guy @Shudaffi informed me that the IDE actually provides shortcut keys for adding parent containers (place the cursor on the Widget, then Alt + Enter, select the parent container to add), which is very convenient.

Before I really don’t know, special thanks big guy: @shu Dafei

Therefore, this article was reedited to focus on how to use extension functions in a different coding style to solve the problem of the deep level of nesting of flutter. I hope you enjoy it!