Dart Basic Directory

1.1 Mind mapping

1.2 Dart foundation will be explained in five parts:

one Mainly explain keywords, variables, built-in types, operators, control flow statements
two I’m going to focus on functions
three Main Lecture class
four Covers generics, libraries, and visibility
five This section describes asynchronous support and exceptions

Second, the function

Dart is a true object-oriented language, even its functions are objects and have their type Function. This also means that functions can be assigned to variables or passed as arguments to other functions. You can also call an instance of the Dart class as a method. For more information, see Callable Classes. Here is an example of a function implementation:

bool isNoble(int atomicNumber) {
  return_nobleGases[atomicNumber] ! =null;
}
Copy the code

While declaring types in the public API is recommended in Effective Dart, the function is still usable by omitting the type declaration (not recommended) :

isNoble(atomicNumber) {
  return_nobleGases[atomicNumber] ! =null;
}
Copy the code

If the function has only one expression, you can use the shorthand syntax:

bool isNoble(intatomicNumber) => _nobleGases[atomicNumber] ! =null;
Copy the code

=> _expr_ syntax is {return _expr_; }. The => notation is sometimes called arrow syntax.

Tip: in the arrow (=>) and semicolon (;) Only one expression, not a statement, can be used between. For example, you cannot use an if statement, but you can use conditional expressions

Methods can have two types of parameters in Dart: required and optional, with the required type parameter coming first, followed by the optional type parameter. The named optional parameter can also be marked “@required”.

2.1 Functions are first-class objects

Functions are also objects and have their type Function, which means functions can be assigned to variables or passed as arguments to other functions. A function can take arguments to another function. Such as:

void printElement(int element) {
  print(element);
}
var list = [1.2.3];
// Pass printElement as an argument.
list.forEach(printElement);
Copy the code

In Java callback functions, such as View’s onClickListener, you define an interface, but in Dart, you can specify a callback method directly to the calling method, which executes the callback at the appropriate time.

// In the first case, the caller is not sure what the return value and parameters of the callback function are
void setListener(Function listener){
    listener("Success");
}
// The second method returns void and takes a String
void setListener(void listener(String result)){
    listener("Success");
}

// Type definition defines a method that returns void and takes a String as a type.
typedef void Listener(StringThe result);void setListener(Listener listener){
  listener("Success");
}
Copy the code

Similarly we can assign a function to a variable, for example:

var loudify = (msg) => '!!!!!!${msg.toUpperCase()}!!!!!!!!! ';
assert(loudify('hello') = ='!!!!!! HELLO !!! ');
Copy the code

Anonymous functions are used in the example. More on that later.

2.2 Optional function parameters

Optional parameters can be named parameters or positional parameters, but a parameter can be modified in either way.

2.2.1 Naming This parameter is optional

When calling a function, you can use the specified named parameter _paramName_: _value_. Such as:

enableFlags(bold: true, hidden: false);
Copy the code

Use {_param1_, _param2_,… } to specify named arguments:

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, boolhidden}) {... }Copy the code

The expressions used to create Flutter instances can be complex, so the widget constructor uses only named parameters. This makes the expression for creating the instance easier to read. Using the @required annotation to indicate that the parameter is a named parameter of required nature can be used in any Dart code (not just Flutter).

const Scrollbar({Key key, @required Widget child})
Copy the code

The Scrollbar is a constructor at this point, and the parser prompts an error when the child argument is missing. Required is defined in the Meta Package. Dart or some other package that exports meta, Such as package: Flutter Flutter/material. The dart.

2.2.2 Position This parameter is optional

Marking the parameters by placing them in [] is optional, the parameters should be selected in order, and [] should be placed last:

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if(device ! =null) {
    result = '$result with a $device';
  }
  return result;
}
Copy the code

Here is an example of calling the above method without optional arguments:

assert(say('Bob'.'Howdy') = ='Bob says Howdy');
Copy the code

Here is an example of calling the above method with optional arguments:

assert(say('Bob'.'Howdy'.'smoke signal') = ='Bob says Howdy with a smoke signal');
Copy the code

Dart types are optional, that is, function types, parameter types, and variable types are omitted:

sum(a, b, c, d) {// Function parameter types and return value types can be omitted
  return a + b + c + d;
}
main() {
  print('${sum(10.12.14.12)}');// It is running properly
}
Copy the code

The sum function has neither a return type nor a parameter type. Some people might wonder what happens if the sum function takes a String as its last parameter. The answer is: the static type checking analysis is fine but the compilation run is not.

sum(a, b, c, d) {
  return a + b + c + d;
}

main() {
  print('${sum(10.12.14."12312")}');Static check The type check is normal, but the operation is abnormal
}

// Run the result
Unhandled exception:
type 'String' is not a subtype of type 'num' of 'other' // Keep this subtype mismatch problem in mind, as we will analyze the subtype in more detail later and see this exception frequently in Dart and Flutter development.
Process finished with exit code 255
Copy the code

While the optional type makes the entire code concise and dynamic on one hand, it makes static checking types difficult to parse on the other. But it also removes the type-based function overloading feature from DART. We all know that function overloading is a common syntactic feature in static languages, but it is not supported in DART. For example, in other languages we use constructor reloads for scenarios where objects are constructed in multiple ways. Dart does not support constructor reloads, so dart introduces the concept of named constructors to address this problem. So why does the optional type syntax feature conflict with function overloading? We can use contradiction and assume that Dart supports function overloading, so we might have the following code:

class IllegalCode {
  overloaded(num data) {
  }
  overloaded(List data){// Assume that function overloading is supported, which is actually illegal
  }
}

main() {
    var data1 = 100; 
    var data2 = ["100"];
    // Because the type in DART is optional, there is no way to tell which overloaded function the code below actually calls.
    overloaded(data1);
    overloaded(data2);
}
Copy the code

On a personal note, dart actually supports type-based function overloading now, if only in terms of optional types, because DART has type derivation. If DART can derive the datA1 and datA2 types described above, then the overloaded functions can be matched based on the derived types. That’s what Kotlin did. Take Kotlin for example:

fun overloaded(data: Int) {
    //....
}
fun overloaded(data: List<String>) {
   //....
}
fun main(args: Array<String>) {
    val data1 = 100 // Here Kotlin also uses type derivation to Int
    val data2 = listOf("100")// Kotlin also uses type derivation as List
      
    // So the following overloaded function calls make sense in Kotlin
    overloaded(data1)
    overloaded(data2)
}
Copy the code

In fact, Dart has the ability to support function overloading, as mentioned on Github. For details, please refer to issue 26488 of Dartlang


But why doesn’t DART support function overloading? In fact, not lack of ability to support, but not necessary. In fact, many modern languages like GO and Rust have no function overloading. However, dart also supports default parameters, and function overloading can be confusing, as in JavaThreadClass 7 or 8 constructor overloads put together, it’s confusing, detailPlease have a look at

2.2.3 Are parameters in a function passed by value or by reference

As mentioned above, when we call a function with parameters, we pass the actual parameters to the formal parameters. However, in a programming language, there are two cases of passing in this process, value passing and reference passing. Let’s look at how value passing and reference passing are defined and distinguished in programming languages.

Pass by value means that a copy of the actual parameter is passed to the function when the function is called, so that the actual parameter is not affected if the parameter is changed. Pass by reference refers to that the address of the actual parameter is directly passed to the function when the function is called. Then the modification of the parameter in the function will affect the actual parameter.

There has been a lot of discussion about this issue, and it seems that many programmers have a different understanding of this issue, and many of them get it wrong. Others may know that parameter passing in Dart is value passing, but can’t say why. Before we dive into this, it’s worth correcting some of your previous misconceptions. If you have the following thoughts, then you need to read this article.

  1. The difference between value passing and reference passing is what is passed. If it is a value, it is value passing. If it’s a reference, it’s reference-passing.
  2. Dart is reference passing.
  3. If the parameter is a normal type, it is passed by value; if it is an object, it is passed by reference.

As we all know, Dart allows you to define parameters when you define a method, such as the above optional parameters. In programming language, parameters are formal and actual parameters.

Formal arguments: Arguments used when defining the name and body of a function to receive arguments passed in when the function is called. Actual arguments: When a function with arguments is called, there is a data passing relationship between the calling function and the called function. When a function is called in the calling function, the arguments in parentheses after the function name are called “actual arguments.”

Here’s a quick example:

void main() {
  // The actual parameter is Hollis
  sout("Hollis");
}

void sout(String name) {
  // The formal argument is name
  print(name);
}
Copy the code

The actual parameters are what is actually passed when a parameterized method is called, while the formal parameters are the parameters used to receive the contents of the arguments. As mentioned above, when we call a function with parameters, we pass the actual parameters to the formal parameters. However, in a programming language, there are two cases of passing in this process, value passing and reference passing. Let’s look at how value passing and reference passing are defined and distinguished in programming languages. With this concept in mind, you can then write code to see if Dart is passed by value or by reference. Here is the simplest code:

void main() {
  int i = 10;
  pass(10);
  print("print in main , i is $i");
}

void pass(int j) {
  j = 20;
  print("print in pass , j is $j");
}

// Output the result
print in pass , j is 20
print in main , i is 10
Copy the code

As you can see, the modification of the value of j inside the pass method does not change the value of the actual parameter I. So, based on the above definition, one concludes that Dart’s method delivery is value delivery. But it didn’t take long for people to question it (so don’t jump to conclusions). . They then move out the following code:

void main() {
  User user = User();
  user.setName("jame");
  pass(user);
  print("print in main , user name is ${user.name}");
}

class User {
  String name;

  void setName(String name) {
    this.name = name; }}void pass(User user) {
  user.setName("hollis");
  print("print in pass , user name is ${user.name}");
}

// Output the result
print in pass , user name is hollis
print in main , user name is hollis

Copy the code

The value of the argument was changed after the pass method was executed. So, based on the above two pieces of code, someone came to a new conclusion: Dart’s method passes ordinary types by value and object types by reference. But that statement is still wrong. The parameter type is passed as an object parameter:

void main() {
  String name = "jame";
  pass(name);
  print("print in main , name is $name");
}

void pass(String name) {
  name = "Hollis";
  print("print in pass , name is $name");
}

// Output the result
print in pass , name is Hollis
print in main , name is jame
Copy the code

What explains this? An object is also passed, but the value of the original parameter is not changed. Is the passed object changed to the value passed? The three examples above show different results, which is why many people are confused about Dart delivery types. In fact, I want to tell you that there is nothing wrong with the above concept, just the code example. So let me just draw the big picture again, and then I’ll give you some really good examples.

Pass by value means that a copy of the actual parameter is passed to the function when the function is called, so that the actual parameter is not affected if the parameter is changed.

Pass by reference refers to that the address of the actual parameter is directly passed to the function when the function is called. Then the modification of the parameter in the function will affect the actual parameter.


So, let me summarize for you what’s important about the difference between value passing and reference passing.

In the pass examples we looked at above, we only looked at whether the actual parameter content changed. If the User object is passed, we try to change the value of its name property and then check to see if it has changed. In fact, the experimental method is wrong, of course, the conclusion is problematic.

Why is the experiment wrong? Here’s a visual example. Take a closer look at value passing and reference passing, and you’ll see why it’s wrong.

You have a key, and if you just give your friend your key when he wants to go to your house, that’s pass-by. In that case, if he did something to the key, like he engraved his name on it, then when the key is returned to you, your key will have his engraved name on it.

You have a key, and when your friend wants to go to your house, you give him a duplicate key, and you still have your own key, and that’s value passing. In this case, what he does to the key doesn’t affect the key in your hand.

But, in either case, your friend takes the keys you gave him, enters your house, and smashes your TV. Do you think you’ll be affected? When we change the value of the name attribute of the user object in the pass method, we are “hitting the TV”.

Take the example above, where we actually change the parameters and see what happens. Okay?

void main() {
  User user = User();
  user.setName("jame");
  pass(user);
  print("print in main , user name is ${user.name}");
}

class User {
  String name;

  void setName(String name) {
    this.name = name; }}void pass(User user) {
  user = User();
  user.setName("hollis");
  print("print in pass , user name is ${user.name}");
}
Copy the code

When we create a User object in main, we create a chunk of memory in the heap that holds data such as name. Hollis then holds the address 0x123456 for that memory. When you try to call the pass method and pass user as an actual parameter to the formal parameter user, the address 0x123456 is passed to user, and the parameter user also refers to the address. Then when you modify the parameter in the pass method, user = new user (); , a chunk of 0X456789 will be recreated and assigned to user. Any subsequent changes to user will not change the contents of memory 0X123456.

What is this transfer above? It’s definitely not reference-passed. If it were, the reference to the actual parameter would also point to 0X456789 when user=new user (), but it doesn’t.

We also know by concept that we are passing a copy of the address of the reference to the actual parameter to the formal parameter. So, the above argument is actually value passing, passing the address referenced by the argument object as the value to the formal argument.

Let’s go back to the “smash the TV” example and see what happened in that case.

Similarly, the address 0X1213456 of the actual parameter is copied to the parameter as it is passed, except that in this method, the parameter itself is not modified, but the contents of the address held by the parameter are modified.

So the difference between value passing and reference passing is not what is passed. It is whether or not the argument is copied to the parameter. When determining whether the content of an argument is affected, it depends on what is being passed. If you pass an address, it depends on whether the address changes, not the object to which the address refers. It’s like a key to a house.

So, why is it that the String is passed and the User is not the same? We use name = “hollis” in the pass method; Trying to change the value of name directly changes the address referenced by name. Because this code will create a new String, passing the reference to name, which is equivalent to name = new String(“hollischuang”); . The original “Hollis” string is still held by the argument, so it is not modified to the value of the actual argument.

So Dart is actually passing by value, but in the case of an object parameter, the content of the value is a reference to the object, and in a simpler way, Dart is passing by value, and that value is actually a reference to the object.

2.3 Default Parameter Values

When defining methods, you can use = to define default values for optional arguments. The default value can only be compile-time constants. If no default value is provided, the default value is null. The following is an example of setting optional parameter defaults:

/// Set the [bold] and [Hidden] flags...
void enableFlags({bool bold = false.bool hidden = false{...}) }// bold is true; Hidden value is false.
enableFlags(bold: true);
Copy the code

Not recommended: Older versions of code may use a colon (:) instead of = to set parameter defaults. The reason is that originally named arguments only supported:. This support may be abandoned. It is recommended to specify the default value with =.

The following example demonstrates how to set default values for positional parameters:

String say(String from, String msg,
    [String device = 'carrier pigeon'.String mood]) {
  var result = '$from says $msg';
  if(device ! =null) {
    result = '$result with a $device';
  }
  if(mood ! =null) {
    result = '$result (in a $mood mood)';
  }
  return result;
}

assert(say('Bob'.'Howdy') = ='Bob says Howdy with a carrier pigeon');
Copy the code

List or map can be passed as defaults. The following example defines a method, doStuff(), and specifies default values for list and Gifts, respectively.

void doStuff(
    {List<int> list = const [1.2.3].Map<String.String> gifts = const {
      'first': 'paper'.'second': 'cotton'.'third': 'leather'{}})print('list:  $list');
  print('gifts: $gifts');
}
Copy the code

2.4 Anonymous Functions

First, what are anonymous functions? In a nutshell: most methods have names, such as main() or printElement(). You can create a method without a name, call it an anonymous function, or a Lambda expression or Closure Closure. You can assign an anonymous method to a variable and then use it, such as adding the variable to or removing it from a collection.

([Type] param1,...). { codeBlock; };Copy the code

Anonymous methods look similar to named methods in that arguments can be defined between parentheses, separated by commas. The following code defines an anonymous method that takes only one parameter item and has no parameter type. This function is called for each element in the List, printing a string of element positions and values:

import 'dart:core';
void main() {
  var list = ['Pharmacist Huang'.'John Steinbeck'.'Old Urchin'];
  list.forEach((item) {
    print('${list.indexOf(item)}: $item'); // Output: 0: Huang Yaoshi 1: Yang Guo 2: old urchin
  });
  // If there is only one line in the function body, you can use arrow syntax:
  list.forEach(
          (item) => print('${list.indexOf(item)}: $item')); // Output: 0: Huang Yaoshi 1: Yang Guo 2: old urchin
}
Copy the code


2.5 Main function (top-level function)

Dart allows you to define top-level functions that are not encapsulated in a class or object. All applications have at least one top-level function, the main() function. The main() function returns a null value and takes an optional List

. Here is the main() function for the Web application:

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me! '
    ..onClick.listen(reverseText);
}
Copy the code

Tip: in the code above.. The syntax is cascade. With cascading invocation, you can simplify multiple operations on a single object.

Here is a command line application of the main() method with input parameters:

void main(List<String> arguments) {
  print(arguments);
  assert(arguments.length == 2);
  assert(int.parse(arguments[0= =])1);
  assert(arguments[1] = ='test');
}
Copy the code

You can define and parse command-line arguments using the ARgs Library.

2.6 Lexical scoping

Dart is a lexical scoped programming language, which means that the scope of a variable is fixed. In short, the scope of a variable is defined at code time. Enclosed in curly braces is the scope within which the variable is visible. The following example deals with the variable scope of multiple nested functions:

bool topLevel = true;
void main() {
  var insideMain = true;
  void myFunction() {
    var insideFunction = true;
    void nestedFunction() {
      var insideNestedFunction = true;
      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction); }}}Copy the code

Note that nestedFunction() has access to all variables up to the top-level scope variable.

This might seem a bit tricky, but Lexical scope scoping may be unfamiliar to you, but it’s the most familiar stranger, so let’s start with the following code.

void main() {
  var a = 0;
  var a = 1; //  Error:The name 'a' is already defined
}
Copy the code

As you must have noticed, we made an obvious mistake in this code. The variable a is defined twice, and the compiler will tell us that the variable name a has already been defined. This is because each of our variables has its lexical scope, and only one variable named A is allowed to exist in the same lexical scope, and syntactic errors can be reported at compile time. This makes sense because if there are two a variables with the same name in a Lexical scoping, then it is syntactically impossible to tell which A you want to access.

In the code above, we define a twice in the lexical scope of main

It just needs to be modified

void main() {
  var a = 1;
  print(a); / / = > 1
}
var a = 0;
Copy the code

We can normally print out that a is 1. The simple explanation is var a = 0; Is a variable defined in the dart file’s semantiscoping, and var a = 1; Function is Object first, it is easy to prove that the method (Function) is an Object.

print( (){} is Object ); // true
Copy the code

(){} is an anonymous function and we can see that the output is true. Function is Object.

void main() {
  var name = 'Vadaski';
  
  var printName = (){
    print(name);
  };
}
Copy the code

As you can clearly see, we can define a new method inside main and assign the method to a variable printName. But if you run this code, you won’t see any output, and that’s why. In fact, once we’ve defined printName here, we’re not really going to execute it. We know that to execute a method, you need to use XXX() to actually execute it.

void main() {
  var name = 'Vadaski';
  
  var printName = (){
    print(name);
  };
  
  printName(); // Vadaski
}
Copy the code

The example above is very common, where the externally defined variable name is accessed inside printName. That is, the internal variables of a Lexical scoping can be accessed by the external variables defined in the Lexical scoping. Function + semantiscoping is ok for internal access to external ** defined variables, and it’s easy to wonder if external access to internally defined variables is allowed. For normal access, this would look like the following.

void main() {
  
  var printName = (){
    var name = 'Vadaski';
  };
  printName();
  
  print(name); // Error: Undefined name' name'
}
Copy the code

The variable defined in printName is not visible to the variables in main. Dart, like JavaScript, has chained scope, meaning that a child scope can access variables in a parent (or even an ancestor) scope and not vice versa. As we can see from the above example, the access rules actually exist as a chain. A new scope can be opened in one scope, and variables with same names can be allowed in different scopes. What rules do we use to access a variable in a scope?

void main() {
  var a = 1;
  firstScope(){
    var a = 2;
    print('$a in firstScope'); //2 in firstScope
  }
  print('$a in mainScope'); //1 in mainScope
  firstScope();
}
Copy the code

In the example above, we can see that the variable A is defined in both main and firstScope. We print in firstScope, prints 2 in firstScope and print in main prints 1 in mainScope. We can already summarize the rule: the nearest is first. If you access a variable in a scope, it first looks to see if the variable is already defined in the current scope, and if so, it is used. If the current scope does not find the variable, it looks for it in the scope above it, and so on down to the original scope. If this variable does not exist on any scope chain, Error: Undefined name’ name’ is raised.

Dart scope variables are statically determined.

void main() {
print(a); // Local variable 'a' can't be referenced before it is declared
var a;
}
var a = 0;
Copy the code

We can see that the variable A exists in main’s parent scope and has been assigned a value, but we also define a variable ain main’s scope. If a is not specified within the scope of print, the scope of a is not specified within the scope of print. If a is not specified within the scope of print, the scope of A is not specified within the scope of print. Local variable ‘a’ can’t be referenced before it is declared.

2.7 Lexical closuresClosure

With that in mind, we can now look at the definition of Closure.

A closure is a function object that has access to variables in its lexical scope, even when the function is used outside of its original scope. A closure is a function object that can access variables in its lexical scope even if the function object is called outside its original scope.

2.7.1 Stateless Functions Usually when we execute a function, it is stateless. You might say, does the function have any states? Let’s look at an example.

void main() {
  printNumber(); / / 10
  printNumber(); / / 10
}
void printNumber(){
  int num = 0;
  for(int i = 0; i < 10; i++){
    num+ +; }print(num);
}
Copy the code

The code above is very predictable, it will print 10 twice, and when we call a function many times, it will still get the same output. However, once we understand that Function is Object, how should we look at Function execution from an Object perspective? Apparently printNumber (); We create a Function object, but we don’t assign it to any variable, next time a printNumber(); A new Function is actually created, and both objects execute the method body once, so you get the same output. Stateless functions are easy to understand. We can now look at stateful functions.

void main() {
  var numberPrinter = (){
    int num = 0;
    return() {for(int i = 0; i < 10; i++){
        num+ +; }print(num);
    };
  };
  
  var printNumber = numberPrinter();
  printNumber(); / / 10
  printNumber(); / / 20
}
Copy the code

This code also executes printNumber() twice; Whereas we get a different output 10,20. It’s starting to smell like a state. But it still seems a little hard to understand, so let’s look at it layer by layer.

var numberPrinter = (){
    int num = 0;
    /// execute function
  };
Copy the code

First we define a Function object and hand it over to numberPrinter management. In the Function scoping that was created, a num variable was defined and assigned to 0.

Note: this method is not executed immediately, but only when numberPrinter() is called. So num doesn’t exist at this point.

return() {for(int i = 0; i < 10; i++){
        num+ +; }print(num);
};
Copy the code

It then returns a Function. This Function takes num from its parent scope, increments it by 10, and prints the value of num.

var printNumber = numberPrinter();
Copy the code

We then create the Function object by calling numberPrinter(), which is a Closure! This object actually performs the numberPrinter we just defined, and defines an int num in its internal scope. And then it returns a method to printNumber.

In fact, the anonymous Function returned is another closure.

Then we do the first printNumber(), which will retrieve the num variable stored in the closure, and do the following.

// num: 0
for(int i = 0; i < 10; i++){
    num+ +; }print(num);
Copy the code

At first, the scope of printNumber stored num as 0, so after 10 increments, num was 10. Finally, print printed 10. The second execution of printNumber() uses the same numberPrinter object, whose num is 10 after the first execution, so it increments from 10 after the second execution, So the result of print is 20. The printNumber serves as a closure throughout the call. It holds the state of the internal num, and as long as the printNumber is not reclaimed, no objects inside it will be removed by the GC.

So we also need to be aware that closures can cause memory leaks or create memory stress problems.

2.7.3 What is a closure

Coming back, our definition of closures should make sense.

A closure is a function object that can access variables in its lexical scope even if the function object is called outside its original scope.

In our example, our num is defined inside a numberPrinter, but we can access the variable externally by returning Function. Our printNumber keeps num all the time. When we use closures, we can simply think of them as three phases. In this phase, we defined Function as a closure, but we didn’t actually execute it.

void main() {
  var numberPrinter = (){
    int num = 0;
    return() {print(num);
    };
  };
Copy the code

At this point, since we only defined the closure and did not execute it, the num object does not exist. Create a stage

var printNumber = numberPrinter();
Copy the code

At this point, we actually execute the contents of the numberPrinter closure and return the execution result, where num is created. In this case, num will remain as long as printNumber is not GC. Access stage

printNumber();
printNumber();
Copy the code

We can then somehow access the contents of the numberPrinter closure. (Num is indirectly accessed in this example.)

2.7.4 Closure application

If we just understand the concept, we might forget it. How can Closure be used? Execute the method where the object is passed let’s say we have a problem with the content of a Text Widget and show us an Error Widget. At this point, I want to print this out to see what’s going on, you can do that.

Text((){
    print(data);
    returndata; } ())Copy the code

Isn’t it amazing that there is such an operation?

Tip executes the closure contents immediately: here we execute the closure contents immediately using the closure syntax (){}() and return our data.

Even though Text only allows us to pass a String, I can still do print. Another case is that if we want to execute certain statements only in Debug mode, we can do so through closure with assertions.

assert(() {
   child.owner._debugElementWasRebuilt(child);// execute some code
   return true; } ());Copy the code

To explain, first assert assertions are enabled only in Debug mode, so the content in the assertion can be executed only in Debug mode. Then we know that the Function() call will execute, so here we immediately execute the contents of the closure via the anonymous closure (){}() and return true to the assertion so that it won’t hang. This enables only the statements within the closure to be executed in Debug mode. Implementing the policy pattern Is easy with closure.

void main(){
  var res = exec(select('sum'),1 ,2);
  print(res);
}

Function select(String opType){
  if(opType == 'sum') return sum;
  if(opType == 'sub') return sub;
  return (a, b) => 0;
}

int exec(NumberOp op, int a, int b){
  return op(a,b);
}

int sum(int a, int b) => a + b;

int sub(int a, int b) => a - b;

typedef NumberOp = Function (int a, int b);
Copy the code

With the SELECT method, we can dynamically select the specific method we want to execute. You can run this code here. If you have any experience with Flutter, you should have used ListView.builder. It works well. We pass only one method to the Builder property, from which the ListView can build each of its items. In fact, this is a closure.

ListView.builder({
/ /...
    @required IndexedWidgetBuilder itemBuilder,
/ /...
  })
  
typedef IndexedWidgetBuilder = Widget Function(BuildContext context, int index);
Copy the code

A Flutter defines a Function via a typedef that takes BuildContext and int as arguments and returns a Widget. For such a Function we define it as IndexedWidgetBuilder and return the widgets inside it. This enables external scopes to access widgets defined within the Scope of IndexedWidgetBuilder, thereby implementing the Builder pattern.

Lazy loading of listViews is also an important feature of closures

2.8 the return value

2.8.1. All functions return a value. If no return value is explicitly specified, the function is added implicitlyreturn null;Statements.
foo() {}
assert(foo() == null);
Copy the code


2.8.2 The returned value isvoidCan be omittedvoidKeyword (not recommended in development).

The return value of a function can be void, null, or a concrete object. If no return value is specified, the function returns NULL. Dart, _incrementCounter(), void can be omitted as shown below:

void _incrementCounter(){
  / /...
}
Copy the code

After the operation:

_incrementCounter(){
  / /...
}
Copy the code

We use assert(_incrementCounter()==null); When you test the program, you can see that the function returns null

Function assert(XXX is Funtion) Function assert(XXX is Funtion) The return value must be a concrete type or omitted. If the return value is void, the compiler will give an error.

Examples are as follows:

void testMethod (){
  / /...
}
Copy the code

For example: assert(testMethod () is Function); // The compiler will report an error.

2.9 Test whether functions are equal

Here is an example test of equality between top-level functions, static methods and sample methods:

void foo() {} // Top-level function

class A {
  static void bar() {} // Static method
  void baz() {} // Sample methods
}

void main() {
  var x;
  // Compare top-level functions.
  x = foo;
  assert(foo == x);
  
  // Compare static methods.
  x = A.bar;
  assert(A.bar == x);
  
  // Compare instance methods.
  var v = A(); // The first instance of A
  var w = A(); // Instance number 2 of A
  var y = w;
  x = w.baz;
  // The same instance referenced by both closures (number 2),
  // So they are equal.
  assert(y.baz == x);
  // Two closures reference different instances,
  // So they are not equal.
  assert(v.baz ! = w.baz); }Copy the code

References:

  1. Dart Official Website
  2. 40 minutes Quick Start Dart Basics (Middle)
  3. In-depth understanding of Dart Function & Closure
  4. Dart Syntax: Type System and Generics (part 7)
  5. Is Java passed by value or by reference?