Important concepts

Learn the Dart language based on the following facts and concepts:

  • Anything stored in a variable is oneobjectAnd all objects are corresponding to oneclassThe instance. Whether it’s numbers, functions andnullThey’re all objects. All objects inherit fromObjectClass.
  • Although Dart is strongly typed, Dart can infer types, so type annotations are optional. In the code above,numberBe extrapolated forintType. To make it clear that no type is required,A special type is requireddynamic
  • Dart supports generics, such asList <int>(integer list) orList <dynamic>(List of objects of any type).
  • Dart supports top-level functions (e.gThe main ()), again the function is bound to a class or object (respectivelyStatic functionsExamples of function). And support for creating functions within functions (nestedLocal function).
  • Similarly, Dart supports top-levelvariableAgain, variables are bound to classes or objects (static and instance variables). Instance variables are sometimes called fields or properties.
  • Unlike Java, Dart does not have the keywords “public,” “protected,” and “private.” If an identifier begins with an underscore (_), it is private relative to the library. For more information, reference libraries and visibility.
  • identifierStart with a letter or underscore (_) followed by any combination of letters and numbers.
  • Included in the Dart syntaxExpressions(with runtime values) andStatements(No runtime value). For example,Conditional expression condition ? expr1 : expr2The value of theta could be thetaexpr1expr2. With theIf – else statementsIn contrast, if-else statements have no value. A statement usually contains one or more expressions, whereas expressions cannot contain statements directly.
  • The Dart tool prompts two types of questions:Warning _ and _ errors. A warning simply indicates that the code may not work properly, but it does not prevent the program from executing. The error can be a compile-time error or a run-time error. Compile-time errors prevent code execution; Runtime errors cause code to raise an exception (# Exception) during execution.

A simple Dart program

// Define a function
printInteger(int aNumber) {
  print('The number is $aNumber. '); // Print to console.
}

// The application starts from here.
main() {
  var number = 42; // Declare and initialize a variable.
  printInteger(number); // Call the function.
}
Copy the code

variable

Create a variable and initialize it:

var name = 'Bob';
dynamic name = 'Bob';
String name = 'Bob'; 
Copy the code

The default value

The default value for uninitialized variables is NULL. The default value is null even if the variable is a numeric type, because everything is an object in Dart, and numeric types are no exception.

int lineCount;
assert(lineCount == null);
Copy the code

Final and Const

For variables that are never modified, you can use final or const instead of var or other types. The value of a final variable can only be set once. Const variables are fixed at compile time (Const variables are types that are implicitly Final.) The highest final variable or class variable is initialized the first time it is used.

final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';
Copy the code

Built-in types

The Dart language supports the following built-in types:

  • Number
  • String
  • Boolean
  • List (also known as ListArray)
  • Map
  • Set
  • Rune (used to represent Unicode characters in strings)
  • Symbol

Number

Here’s how to convert a string to a number and vice versa:

// String -> int
var one = int.parse('1');
assert(one == 1);

// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);

// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
Copy the code

String

Strings can be nested with expressions as ${expression}. If the expression is an identifier, {} can be omitted. Dart retrieves the string of the object by calling the toString() method on the object.

var s = 'string interpolation';

assert('Dart has $s, which is very handy.'= ='Dart has string interpolation, ' +
        'which is very handy.');
Copy the code

Create a multi-line string object with three consecutive single or double quotes:

var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";
Copy the code

Using the r prefix, you can create “raw” strings:

var s = r"In a raw string, even \n isn't special.";
Copy the code

Boolean

// Check for empty strings.
var fullName = ' ';
assert(fullName.isEmpty);

// Check the value of 0.
var hitPoints = 0;
assert(hitPoints <= 0);

// Check for null values.
var unicorn;
assert(unicorn == null);

// Check NaN.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
Copy the code

List

Dart inferred that the list type is list 
      
       . If you try to add a non-integer object to this List, the parser or runtime will raise an error. For more information, read Type inference.
      
var list = [1.2.3];
Copy the code

Set

In Dart, a Set is a Set with unique and unnecessary elements. Dart provides a Set literal and a Set type for a Set.

var halogens = {'fluorine'.'chlorine'.'bromine'.'iodine'.'astatine'};
Copy the code

To create an empty Set, use {} preceded by a type argument, or assign {} to a variable of type Set:

var names = <String> {};// Set
      
        names = {}; // This is ok.
      
// var names = {}; // This creates a Map instead of a Set.
Copy the code

Map

In general, a Map is an object used to associate keys and values. Keys and values can be any type of object. A key can only appear once in a Map object. But value can appear more than once. Dart maps are implemented through Map literals and Map types.

var gifts = {
  // Key: Value
  'first': 'partridge'.'second': 'turtledoves'.'fifth': 'golden rings'
};

var nobleGases = {
  2: 'helium'.10: 'neon'.18: 'argon'};var gifts = Map(a); gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = Map(a); nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';
Copy the code

function

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

// Hide the return value and callback parameters and still run
isNoble(atomicNumber) {
  return_nobleGases[atomicNumber] ! =null;
}
Copy the code

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

// => expr syntax {return expr; }. The => notation is sometimes called arrow syntax.
bool isNoble(intatomicNumber) => _nobleGases[atomicNumber] ! =null;
Copy the code

Optional parameters

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

This parameter is optional

int test2(int x, [int y, int z]) {
  if (y == null || z == null) {
    return x;
  }
  return x + y + z;
}

// tmp = 1
var tmp = test2(1);
Copy the code

Default parameter This parameter is optional

// Since optional parameters have default values, there is no need to check whether the parameter is null
int test3(int x, [int y = 3.int z = 100]) {
  return x + y + z;
}

// a = 16, b = 112, c = 15
int a = test3(2.10.4);
int b = test3(2.10);
Copy the code

Named (named) parameters

You can have default parameters

int test4(int x, {int y = 3.int z = 111{})return x + y + z;
}

// c = 15
int c = test4(2, z: 10);
Copy the code

This parameter is optional

To define the function, use {*param1*, *param2*… } to specify named arguments:

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

When calling a function, you can use the specified named parameter *paramName*: *value*. Such as:

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

The Scrollbar is a constructor at this point, and the parser prompts an error when the child argument is missing.

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

Location This parameter is optional

It is optional to mark parameters by placing them in [] :

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

Default Parameter Value

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

The operator

The assignment operator

Use?? The = operator assigns to a variable only if it is null.

// If b is empty, the variable is assigned to b; otherwise, the value of b remains unchanged.b ?? = value;Copy the code

Cascade operators (..)

Cascade operators (..) You can implement a series of operations on the same object. In addition to calling functions, you can also access field properties on the same object. This can often save you the step of creating temporary variables while writing smoother code.

Consider the code:

querySelector('#confirm') // Get the object.
  ..text = 'Confirm' // Call a member variable.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed! '));
Copy the code

The first calls the function querySelector(), which returns the obtained object. The obtained objects execute the code following the cascade operator in turn, and the return value after the code execution is ignored.

The above code is equivalent to:

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed! '));
Copy the code

Cascade operators can be nested, for example:

finaladdressBook = (AddressBookBuilder() .. name ='jenny'
      ..email = '[email protected]'. phone = (PhoneNumberBuilder() .. number ='415-555-0100'
            ..label = 'home')
          .build())
    .build();
Copy the code

Use cascade operators sparingly in functions that return objects. For example, the following code is incorrect:

var sb = StringBuffer(a); sb.write('foo')
  ..write('bar'); // Error: 'void' does not define 'write' function.
Copy the code

The sb.write() function call returns void, and cascading operations cannot be created on void objects.

abnormal

throw

Here is an example of throwing or throwing an exception:

throw FormatException('Expected at least 1 section');
Copy the code

We can also throw arbitrary objects:

throw 'Out of llamas! ';
Copy the code

Because a thrown exception is an expression, it can be used in => statements, and it can be thrown anywhere else where expressions are used:

void distanceTo(Point other) => throw UnimplementedError();
Copy the code

catch

Catching an exception prevents the exception from being passed on (unless it is rethrown). The exception can be handled by the opportunity to catch it:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}
Copy the code

By specifying multiple catch statements, you can handle code that may throw multiple types of exceptions. The first catch statement that matches the type of exception thrown handles the exception. If a catch statement does not specify a type, the statement can handle thrown objects of any type:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // a special exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Any other exceptions
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handle all exceptions
  print('Something really unknown: $e');
}
Copy the code

class

The constructor

A constructor is declared by creating a function with the same name as its class (alternatively, an additional optional identifier can be attached, as described in named constructors). Create an instance of a class using the most common constructor form, the generate constructor:

class Point {
  num x, y;

  Point(num x, num y) { 
    // Use the 'this' keyword to refer to the current instance.
    this.x = x;
    this.y = y; }}Copy the code

In normal mode, the values of the parameters passed by the constructor are assigned to the corresponding instance variables. Dart’s syntax simplifies this code:

class Point {
  num x, y;

  // Before the constructor body executes,
  // The syntax sugar has set the variables x and y.
  Point(this.x, this.y);
}
Copy the code

Default constructor

Dart provides a default constructor when no constructor is declared. The default constructor takes no arguments and calls the parent class’s no-argument constructor.

Constructors are not inherited

Subclasses do not inherit the constructor of their parent class. A subclass does not declare a constructor, so it has only a default constructor (anonymous, no arguments).

Named constructor

We can use named constructors to implement multiple constructors for a class. We can also use named constructors to more clearly indicate the purpose of a function:

class Point {
  num x, y;

  Point(this.x, this.y);

  // Name the constructor
  Point.origin() {
    x = 0;
    y = 0; }}Copy the code

Remember that constructors cannot be inherited, which means that the named constructor of the parent class cannot be inherited by subclasses. If you want to create a subclass using a named constructor defined in the parent class, you must implement that constructor in the subclass.

Call the parent class non-default constructor

By default, the constructor of a subclass automatically calls the default constructor of the parent class (anonymous, with no arguments).

If there is no anonymous, parameterless constructor in the parent class, the other constructors of the parent class need to be called manually. After the colon (:) of the current constructor and before the function body, the parent constructor is declared to be called.

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person'); }}class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}

// Print: in Person in Employee
Copy the code

Initialization list

In addition to calling the superclass constructor, instance variables can be initialized before the constructor body is executed. Parameter initializations are separated by commas.

// Before the constructor body executes,
// Set the instance variable through the initial list.
Point.fromJson(Map<String.num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x.$y) ');
}
Copy the code

Warning: The right side of the initializer cannot access this.

During development, you can use Assert to validate the input initializer list.

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x.$y) ');
}
Copy the code

Factory constructor

The factory keyword is used when executing the constructor does not always create a new instance of the class. For example, a factory constructor might return an instance of a cache, or it might return an instance of a subclass.

The following example demonstrates the factory constructor that returns an object from the cache:

class Logger {
  final String name;
  bool mute = false;

  _cache is a private attribute, as we know from the named _.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if(! mute)print(msg); }}Copy the code

Tip: The factory constructor cannot access this.

The factory constructor is called like any other constructor:

var logger = Logger('UI');
logger.log('Button clicked');
Copy the code

methods

Getter and Setter

Getters and setters are special methods for reading and writing object properties. Recall from the previous example that each instance variable has an implicit Getter and, typically, a Setter. Getters and setters are implemented using the GET and set keywords to create additional properties for the instance.

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculation attributes: right and bottom.
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3.4.20.15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == - 8 -);
}
Copy the code

Abstract methods

Instance methods, getters, and setter methods can be abstract, defining interfaces that are not implemented and left to other classes to implement. Abstract methods exist only in abstract classes.

Define an abstract function using a semicolon (;) Instead of the function body:

abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide method implementation, so methods here are not abstract methods...}}Copy the code

Calling an abstract method causes a runtime error.

Implicit interface

Each class implicitly defines an interface that contains all the instance members of the class and the interfaces that implement them. If you want to create A class A that supports the API of class B but does not need to inherit the implementation of B, you can implement the interface of B through A.

A class implements one or more interfaces through the implements keyword and implements the REQUIRED APIS for each interface. Such as:

/ / the person class. The implicit interface contains the greet() method declaration.
class Person {
  // is contained in the interface, but is only visible in the current library.
  final _name;

  // Not included in the interface, because this is a constructor.
  Person(this._name);

  // is contained in the interface.
  String greet(String who) => 'Hello, $who. I am $_name. ';
}

// Implementation of the Person interface.
class Impostor implements Person {
  get _name => ' ';

  String greet(String who) => 'Hi $who. Do you know who I am? ';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}
Copy the code

The following example demonstrates how a class implements multiple interfaces:

class Point implements Comparable.Location {}Copy the code

Extending classes (inheritance)

Use the extends keyword to create a subclass and the super keyword to reference a parent class:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  / /...
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  / /...
}
Copy the code

Overriding class member

Subclasses can override instance methods, getters, and setters. You can use the @override annotation to indicate which member you want to override:

class SmartTelevision extends Television {
  	@override
    void turnOn() {

    } 
}
Copy the code

noSuchMethod()

When code tries to use a method or instance variable that does not exist, the noSuchMethod() method is overridden to detect and handle it:

class A {
  // If noSuchMethod is not overridden, accessing a nonexistent instance variable will result in a NoSuchMethodError error.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}'); }}Copy the code

Methods that are not implemented cannot be called unless one of the following conditions is met:

  • The receiver isdynamicStatic type of.
  • Receiver has a static type, which is used to define methods for implementation (which can be abstract), and the dynamic type of receiver hasnoSuchMethod()The implementation andObjectThe implementation is different in the class.

Enumerated type

Enumeration types, also called Enumerations or enums, are special classes that represent a fixed number of constant values.

Use enumerated

Define an enumeration type using the enum keyword:

enum Color { red, green, blue }
Copy the code

Each value in an enumeration has an Index getter method that returns the position of the value in the enumeration type definition (starting at 0). For example, the index of the first enumeration value is 0 and the index of the second enumeration value is 1.

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
Copy the code

Using the enumeration’s values constant, get a list of all enumerated values.

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
Copy the code

You can use enumerations in switch statements and get a warning if you do not process all enumerations:

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses! ');
    break;
  case Color.green:
    print('Green as grass! ');
    break;
  default: // Without this, you will see a warning.
    print(aColor); // 'Color.blue'
}
Copy the code

Enumerated types have the following restrictions:

  • Enumerations cannot be subclassed, mixed, or implemented.
  • Enumerations cannot be explicitly instantiated.

Add functionality to the class: Mixin

Mixins are a way to reuse class code at different levels without inheritance.

Mixins are used by using with followed by one or more mixins. The following example demonstrates two classes that use mixins:

class Musician extends Performer with Musical {
  / /...
}

class Maestro extends Person with Musical.Aggressive.Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true; }}Copy the code

Implement a Mixin by creating a class that inherits from Object and has no constructor. If mixins do not want to be used as regular classes, use the keyword Mixin instead of class. Such as:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self'); }}}Copy the code

Specify that only certain types of mixins can be used – for example, mixins can call methods not defined by the Mixin itself – use on to specify the parent class types that mixins can use:

mixin MusicalPerformer on Musician {
  / /...
}
Copy the code

Class variables and methods

Use the static keyword to implement class-scoped variables and methods.

A static variable

Static variables (class variables) are useful for class-level state:

class Queue {
  static const initialCapacity = 16;
  / /...
}

void main() {
  assert(Queue.initialCapacity == 16);
}
Copy the code

Static variables are initialized only when they are used.

The generic

In the API documentation you can see that the actual type of the underlying array type List is List

. <… The > symbol marks the List as a generic (or parameterized) type. This type has formal parameters. Typically, a single letter is used to represent type parameters, such as E, T, S, K, and V.

Why generics

Generic support is often needed for type safety, and its benefits go beyond keeping code running:

  • Specifying generic types correctly can improve code quality.
  • Using generics reduces duplication of code.

If you want the List to support only strings, you can declare it as List

(read “List of strings”). Then, when a non-string is assigned to the list, the development tool can detect a possible error in doing so. Such as:

var names = List<String> (); names.addAll(['Seth'.'Kathy'.'Lars']);
names.add(42); / / error
Copy the code

Another reason to use generics is to reduce duplicate code. Generics can define the same implementation across multiple types, while continuing to use the code analysis capabilities provided by check patterns and static analysis tools. For example, suppose you create an interface for caching objects:

abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}

// We found that we needed a string interface with the same functionality, so we created another interface:
abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

// We need a digital type interface with the same functionality... You get the idea here.
Copy the code

Generics save you the trouble of creating all these interfaces. Replace the above interface by creating an interface with generic parameters:

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}
Copy the code

In the above code, T is an alternate type. This is a type placeholder that specifies the type when the developer calls the interface.

Use collection literals

List, Set, and Map literals can also be parameterized. Parameterized literals are defined similarly to the previous literals. For a List or Set, you only need to prefix the declaration statement with <*type*>, and for a Map, you only need to prefix the declaration statement with <*keyType*, *valueType*>. Here are examples of parameterized literals:

var names = <String> ['Seth'.'Kathy'.'Lars'];
var uniqueNames = <String> {'Seth'.'Kathy'.'Lars'};
var pages = <String.String> {'index.html': 'Homepage'.'robots.txt': 'Hints for web robots'.'humans.txt': 'We are people, not machines'
};
Copy the code

Use the constructor of a generic type

When the constructor is called, use Angle brackets after the class name (<… >) to specify generic types. Such as:

var nameSet = Set<String>.from(names);
Copy the code

Create a map object with key integer and value View:

var views = Map<int, View>();
Copy the code

A collection of generics at run time

Generic types in Dart are hardwired, which means they carry type information with them at run time. For example, check the type of collection at runtime:

var names = List<String> (); names.addAll(['Seth'.'Kathy'.'Lars']);
print(names is List<String>); // true
Copy the code

Tip: Instead, generics in Java are erased, meaning that information about generic type parameters is not present at run time. In Java, you can test if an object is of type List, but you cannot test if it is List

.

Restrict generic types

When using generic types, you can use extends to implement constraints on parameter types.

class Foo<T extends SomeBaseClass> {
  // Implementation goes here...
  String toString() => "Instance of 'Foo<$T> '";
}

class Extender extends SomeBaseClass {... }Copy the code

You can use SomeBaseClass or any subclass of it as a generic argument:

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
Copy the code

You can also specify no generic arguments:

var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
Copy the code

Specifying any non-someBaseclass type results in an error:

var foo = Foo<Object> ();Copy the code

Use generic functions

Initially, Dart’s generics could only be used with classes. New syntax _ generic method _, which allows type arguments on methods and functions:

T first<T>(List<T> ts) {
  // Do some initial work or error checking, then...
  T tmp = ts[0];
  // Do some additional checking or processing...
  return tmp;
}
Copy the code

The first (

) generic here can use the argument T in places like this:

  • Function return value type (T).
  • Parameter type (List<T>).
  • Type of local variable (T tmp).

Libraries and visibility

Import and library directives can be used to create a modular, shareable code base. The library not only provides an API, but also acts as a wrapper around the code: identifiers starting with an underscore (_) are visible only within the library. Each Dart application is a library, although no library instructions are used.

Libraries can be distributed through packages. For information about PUB (the Package Manager integrated with the SDK), see Pub Package and Asset Manager.

Use the library

Import specifies how contents in one library namespace are used in another library. For example, Dart Web applications typically use the Dart: HTML library, which can be imported like this:

import 'dart:html';
Copy the code

The import parameter requires only a URI pointing to the library. For built-in libraries, URIs have their own special DART: scheme. For other libraries, use the system file path or package: scheme. Package: Scenario specifies libraries provided by a package manager, such as the PUB utility. Such as:

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

Tip: URI stands for Uniform Resource Identifier (URI). A URL (Uniform resource Locator) is a common URI.

Specifying the library prefix

If you import two libraries with conflicting identifiers, you can specify a prefix for either or both of them. For example, if library1 and library2 both have an Element class, this can be handled as follows:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Use Element in lib1.
Element element1 = Element(a);// Use Element in lib2.
lib2.Element element2 = lib2.Element(a);Copy the code

Import part of the library

If you only use part of the library, you can choose what you want to import. Such as:

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
Copy the code

Lazy-loaded library

Deferred loading (also known as lazy loading) allows an application to load libraries as needed. Here are some scenarios for using lazy-loaded libraries:

  • Reduce APP startup time.
  • Perform A/B testing, such as trying different implementations of various algorithms.
  • Load little-used features, such as optional screens and dialogs.

To lazily load a library, import it using Deferred As:

import 'package:greetings/hello.dart' deferred as hello;
Copy the code

When needed, call the loadLibrary() function with the library identifier to load the library:

Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}
Copy the code

In the previous code, use the await keyword to suspend code execution until the library is loaded. See Async support for more information about async and await.

You can call loadLibrary() multiple times on a library. But the library is only loaded once.

Note some issues when using lazy loading libraries:

  • Lazy-loaded library constants are not available at import time. Library constants are available only when the library is loaded.
  • Types in the delay library cannot be used when importing files. If you need to use types, consider moving the interface type into another library and having both libraries import it separately.
  • Dart implies thatloadLibrary()Function import to useNamespace of deferred as *In the.loadLibrary()Method returns aFuture.

Specifying the library prefix

If you import two libraries with conflicting identifiers, you can specify a prefix for either or both of them. For example, if library1 and library2 both have an Element class, this can be handled as follows:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Use Element in lib1.
Element element1 = Element(a);// Use Element in lib2.
lib2.Element element2 = lib2.Element(a);Copy the code

Import part of the library

If you only use part of the library, you can choose what you want to import. Such as:

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
Copy the code

Asynchronous support

The Dart library contains many functions that return Future or Stream objects. These functions return immediately after setting up time-consuming tasks, such as I/O operations, and do not wait for time-consuming tasks to complete. Asynchronous programming with async and await keywords. Allows you to do asynchronous operations just like writing synchronous code.

To deal with the Future

You can obtain the result of a Future execution in two ways:

  • useasyncawait.
  • Use the Future API for detailed description, refer to the library overview.

Code that uses the async and await keywords is asynchronous. Although it seems a little bit like trying to synchronize your code. For example, the following code uses await to wait for the execution result of an asynchronous function.

await lookUpVersion();
Copy the code

To use await, the code must be in an asynchronous function (a function that uses the async flag) :

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}
Copy the code

Tip: While an asynchronous function may perform time-consuming operations, it does not wait for them. In contrast, asynchronous functions are executed only when they encounter the first await expression (see more on this). That is, it returns a Future object and resumes execution only after the await expression is completed.

Use try, catch, and finally to handle errors caused by using await in code.

try {
  version = await lookUpVersion();
} catch (e) {
  // React to inability to look up the version
}
Copy the code

You can use await multiple times in an asynchronous function. For example, the following code waits for the result of a function three times:

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
Copy the code

In await * expression *, the value of the expression * is usually a Future object; If not, the value of the expression is automatically wrapped as a Future object. The Future object specifies a promise to return an object. The result of execution of await expression * is the returned object. Await expressions block the execution of code until the desired object is returned.

If using await results in a compile-time error, verify that await is in an asynchronous function. For example, to use await in the application’s main() function, the body of the main() function must be marked async:

Future main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}
Copy the code

Declaring asynchronous functions

A function whose body is marked by the async identifier, that is, a _ asynchronous function _. Add the async keyword to the function to return the Future. For example, consider the following synchronization function, which returns a String:

String lookUpVersion() => '1.0.0';
Copy the code

For example, if a Future implementation would be time-consuming, change it to an asynchronous function that returns a Future.

Future<String> lookUpVersion() async= >'1.0.0';
Copy the code

Note that the function body does not need to use the Future API. Dart creates Future objects if necessary.

If the function does not return a valid value, set its return type to Future

.

To deal with the Stream

When you need to get data values from a Stream, you can do this in one of two ways:

  • useasyncAnd aAsynchronous loopawait for).
  • Using the Stream API, see in the Library Tour for more details.

Tip: Before using await for, make sure your code is clear and you really want to wait for the results of all the streams. For example, UI event listeners should not normally be await for because the UI framework sends an endless stream of events.

Here’s how an asynchronous for loop can be used:

await for (varOrType identifier in expression) {
  // Executes each time the stream emits a value.
}
Copy the code

The value returned by the above expression must be of type Stream. The execution process is as follows:

  1. Wait until the stream emits a value.
  2. Executes the body of the for loop, setting the variable to the emitted value
  3. Repeat 1 and 2 until the flow is closed.

Use a break or return statement to stop receiving data from the stream, thus breaking out of the for loop and unregistering the stream. ** If you encounter a compile-time error while implementing an asynchronous for loop, check to ensure that await for is in an asynchronous function. ** For example, to use asynchronous fo r loops in the application’s main() function, the body of the main() function must be marked async ‘:

Future main() async {
  // ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  // ...
}
Copy the code

For more information on asynchronous programming, see the DART: Async section. Dart Language Asynchrony Support: Phase 1 Dart Language Asynchrony Support: Phase 2, and the Dart Language Specification.

The generator

Consider using the _ generator function _ when you need to lazily produce a series of values. Dart has built-in support for two generator functions:

  • Synchronous: Returns an Iterable object.
  • Asynchronous generator: Returns a Stream object.

You can implement a synchrogenerator function by marking sync* in the function body. Use the yield statement to pass values:

可迭代<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}
Copy the code

You can implement an asynchronous generator function by marking async* in the function body. Use the yield statement to pass values:

Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}
Copy the code

If the generator is recursive, yield* can be used to improve its performance:

可迭代<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1); }}Copy the code