Functions

In Dart, Functions are special kind of Object. It means you can assign function to a variable or pass it to another function as arguments. Here is how a function is defined -

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

Here, bool is the type of return value that the function returns. You can also omit this. So the following is perfectly valid.

isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

Arrow Function

Arrow function is a shorthand syntax for the function. You can use shorthand syntax only if there is one expression (not a statement) to be executed and the evaluated value to be returned. The above example can be rewritten as -

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

The => expr syntax is a shorthand for { return expr; }. The => notation is sometimes referred to as arrow syntax. That's why these functions are called arrow functions.

Only an expression—not a statement—can appear between the arrow (=>) and the semicolon (;). For example, you can’t put an if statement there, but you can use a conditional expression.

Function Parameters

Function parameters are two types -

The required parameters are listed first. Followed by optional parameters.

Optional parameters can be of two types -

Optional Parameters

Optional parameters can be either named or positional but not both. Some APIs — notably Flutter widget constructors — use only named parameters, even for parameters that are mandatory.

Named Parameters

Use square bracket to define named parameters:

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

Now when you call a function, you can specify named parameters using paramName: value. For example:

enableFlags(bold: true, hidden: false);

Positional Parameters

Wrapping a set of function parameters in [] marks them as optional positional parameters:

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

Here’s an example of calling this function without the optional parameter:

assert(say('Bob', 'Howdy') == 'Bob says Howdy');

And here’s an example of calling this function with the third parameter:

assert(say('Bob', 'Howdy', 'smoke signal') == 'Bob says Howdy with a smoke signal');

@required annotation

Although named parameters are optional parameters, you can make them required by adding @required annotation.

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

Now the user must provide value of the parameter child. If someone tries to create a Scrollbar without specifying the child argument, then the analyzer reports an issue.

The @required annotation is defined in meta package. So you need to import that package (package:meta/meta.dart)

This way you can stick to the named parameter syntax and make the parameter required using @required annotation on the parameter of your choice.

Required Parameters

Required parameter appears at the beginning of the parameter list and outside of the optional parameter syntax. In other words, outside of square bracket or curly bracket.

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

In the above example, the parameter from and msg is known as required attribute and user must provide their values when calling the function.

Default Parameter values

Your function can use = to define default values for both named and positional parameters. The default values must be compile-time constants. If no default value is provided, the default value is null.

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}

// bold will be true; hidden will be false.
enableFlags(bold: true);

The next example shows 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');

main() Function

Every app must have a top-level main() function, which serves as the entrypoint to the app. The main() function returns void and has an optional List<String> parameter for arguments.

Here’s an example of the main() function for a web app:

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

Anonymous Function

Most functions are named, such as main() or printElement(). You can also create a nameless function called an anonymous function, or sometimes a lambda or closure. You might assign an anonymous function to a variable so that, for example, you can add or remove it from a collection.

An anonymous function looks similar to a named function— zero or more parameters, separated by commas and optional type annotations, between parentheses.

The following example defines an anonymous function with an untyped parameter, item. The function, invoked for each item in the list, prints a string that includes the value at the specified index.

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

The above example can be rewritten using arrow syntax:

list.forEach(
    (item) => print('${list.indexOf(item)}: $item')
);

Return values

All functions return a value. If no return value is specified, the statement return null; is implicitly appended to the function body.

foo() {}

assert(foo() == null);

Lexical Scope

Dart is a lexically scoped language, which means that the scope of variables is determined statically, simply by the layout of the code. You can “follow the curly braces outwards” to see if a variable is in scope.

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

The main function defines insideMain variable which is accessible anywhere in that function. That means if you define a function inside the main function, that function can have access to anything that the main function defines. So in the above example, the nested function myFunction can access insideMain variable. This philosiphy is similar to what Javascript have.

Lexical 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.

/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);

  // Create a function that adds 4.
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

When you call the function makeAdder(2), an anonymous function is created inside the makeAdder function and gets returned. In this process, a lexical environment is created for newly created function and the value 2 is stored in the variable addBy.

The returned anonymous function is stored in the variable add2. Now, whenever and wherever you use add2, it remembers where it is created and what values it has by looking at it's lexical environment. In our case, the call add2(3) is evaluated as:

(num i) => 2 + 3

Because, it remember what the value of addBy is, in this case the value of addBy is 2