Important concepts

  • Unlike Java, Dart doesn’t have the keywords public, protected, and private. We usually prepend an underscore ==(_)== to denote the constant is only used within this file, it means that members or classes that start with _ are private.
  • After Dart 2, new Widget() is equal to Widget(), it means that new and const keywords were made optional
  • Dart is ==single-threaded==.

Built-in types

Lists (Arrays)

  • In Dart, ==arrays== are List objects.

Useful Methods

  • List.generate()

Variables

Types

根据情况判断哪些场景可以省略类型声明

Type annotating (显式声明)

  • 不能一眼就看出 type 的 public fieldstop-level variables

  • 如果感觉 type 不够明显,就尽管显式声明 type

  • generic invocations 的某些情况:

    var thing = Set<String>();
    
  • 当 Dart 推断类型的结果不是你想要的时候:For example, you may want a variable’s type to be a supertype of the initializer’s type so that you can later assign some other sibling type to the variable.

  • Dart 在很多时候允许省略类型声明,因为它会尝试去推断变量的类型。但是推断也会有失败的时候,这是它会默默地将其类型置为 dynamic。但是这对于别人来说会导致代码的可读性降低。如果真的想用 dynamic 作为变量的类型,最好使用显式声明

  • signatures in function type annotations : 关于 Function 这种 special type。

Omit types (省略声明)

  • private fields 中,可以更多地考虑省略类型声明,前提是变量的类型可以比较容易地推断出来

  • local variables (比如 function)这种作用域(scope)比较小的情况下,可以省略类型声明(使用 var 来代替)

  • 可推断参数类型(inferred parameter types)的函数表达式(function expressions):比如匿名函数,会被立即传入到一个带有某种 type 声明 callback 的方法中,比如

    /* Function's parameter type is inferred based on the type of callback that map() expects */
    // do
    var names = people.map((person) => person.name);
      
    // don't
    var names = people.map((Person person) => person.name);
    
  • Redundant type arguments on generic invocations

    // do
    Set<String> things = Set();
    // don't
    Set<String> things = Set<String>();
    
  • Don’t specify a return type for a setter, because setters always return void in Dart.

声明类型的选择

  • DON’T use the legacy typedef syntax : 关于 defining a named typedef for a function type. 使用 new syntax 一个明显的好处就是 lacks the error-prone misfeature where a single identifier is treated as the parameter’s name instead of its type.
  • CONSIDER using function type syntax for parameters :当 Function 作为 parameters 的时候,如何书写格式能更直观地反映出此 parameter 的 type 是 Function 和它的返回值是什么类型
  • Do annotate with Object instead of dynamic to indicate any object is allowed: 要正确地针对不同的使用场景来使用这两种类型,因为 Two types in Dart permit all values: Object and dynamic
  • 当不需要 async function 返回有效值的时候,使用 Future<void>。在旧的 Dart 版本,可以见到在这种情况下会使用 Future 或者 Future<Null> ,那是因为在旧版本中不支持 void as a type argument。
  • AVOID using FutureOr<T> as a return type :避免增加额外的检查代码,统一将它当成 Future 来处理。

Identifier

const

For variables

  • The const keyword became optional within a constant context in Dart 2.
  • Use const for variables that you want to be compile-time constants. If the const variable is at the ==class level==, mark it static const. Where you declare the variable, set the value to a compile-time constant such as a number or string literal, a const variable, or the result of an arithmetic operation on constant numbers

For constructors

  • 如果一个 class 含有 const constructor , 在创建实例的时候,如果想要这个实例是 compile-time constant ,在调用对象的构造函数时需要显式使用 const 作为 prefix,否则只会创建一个普通的新实例,因为 const constructor 同样也可以作为普通的构造函数来使用。
  • If you have a class where all the fields are final, and the constructor does nothing but initialize them, you can make that constructor const.

covariant

The covariant keyword was introduced in 1.22. It replaces the @check annotation.

  • 这个关键字用来限定参数的子类型

  • can be used in either the superclass or the subclass method, usually the best place is superclass
  • The covariant keyword applies to a single parameter (这里并不是指使用了该 keyword 的 method 只能接受单个 parameter) and is also supported on setters and fields.

Functions

  • 函数也是对象,它的数据类型是 Function ,因此可以赋值给变量,也可以作为参数传递给其他的函数
  • => expr 表示 { return expr; } ,在这个语句后面只能跟==一个==表达式 (expression)。有时候会成为 arrow syntax
  • parameters 可以连类型(tpyes)声明都省略,直接用参数名

Parameters

  • 为了提高可读性,避免使用 positional ==boolean== parameters,应该考虑使用 named arguments, named constructors or named constants
  • 当一个 parameter 存在会被省略的可能,将它设置成 optional parameter 而不是强制用户传入 null 或者空字符串这类值作为参数,这样还可以避免一些 bug。
  • DO use inclusive start and exclusive end parameters to accept a range

Optional parameters

Optional named parameters

如果想要在传入调用函数的参数使用 paramName: value 这种格式,则需要在定义函数的时候使用 {param1, param2, ...} (注意不要漏掉了 {})。使用这种方式来指定参数的时候,如果想让某个参数为必填,则需要在参数类型前使用 @required 来注解,否则参数都是 optional

// define
// 'name' is required; 'age' and 'weight' are optional
void printInfo({@required String name, int age, int weight}) {...}
// call
printInfo(name: "Singor", age: 20);

Optional positional parameters

optional 的参数都放在 [] 里面,否则参数都是 required

// define
// 'name' is required; 'age' and 'weight' are optional
void printInfo(String name, [int age, int weight]) {...}
// call
printInfo('Singor', 20);

Operators

条件表达式 (Conditional expressions)

// 跟 Java 的三目运算符一样
// condition ? expr1 : expr2
var visibility = isPublic ? 'public' : 'private';

// 非空返回
// expr1 ?? expr2
String playerName(String name) => name ?? 'Guest';

Control flow statements

Assert

  • 如果结果为 false 的话,可以中断程序执行
  • ==Assert== statements 不会对 production 的程序造成影响,它只会在 development 起作用

Generics

  • Dart generic types are reified, which means that they carry their type information around at runtime

==Note:== Generics in Java use erasure , which means that generic type parameters are removed at runtime. In Java , you can test whether an object is a List, but you can’t test whether it’s a List<String>

var names = List<String>();
names.addAll(['name', 'age', 'gender']);
print(names is List<String>); // true
  • Restricting the parameterized type, use extends
class Foo<T extends SomeBaseClass> { ... }

在新建实例的时候,可以使用 SomeBaseClass 它本身或者它的子类作为泛型参数,甚至不指明泛型参数也可以。

  • Now, Dart’s generic supports classes, and generic methods allows type arguments on methods and functions.

Classes

  • use ?. instead of . to avoid an exception when the leftmost operand is ==null==

  • 新建对象的时候,从 Dart 2 开始,new 关键字变成是 optional 的,用不用都无所谓

  • Constructing two identical compile-time constants results in a single, canonical instance

    var a = const ImmutablePoint(1, 1);
    var b = const ImmutablePoint(1, 1);
    assert(identical(a, b)); // They are the same instance!
    
  • To get an object’s type at runtime, use Object’s runtimeType property.

  • this keyword: The this keyword refers to the current instance. Use this only when there is a name conflict. Otherwise, Dart style omits the this.

Constructor

Types:

  • Default constructor
  • Named constructor
  • Redirecting constructor
  • Constant constructor
  • Factory constructor
  • The default constructor has no arguments and invokes the no-argument constructor in the superclass.
  • ==Named constructors== is for implementing multiple constructors for a class or to provide extra clarity. 如果想让一个类有多个构造函数,在 Dart 中的语法会有所不同。
  • Constructors are not inherited, which means that a superclass’s named constructor is not inherited by a subclass.
  • By default, a constructor in a subclass calls the superclass’s unnamed, no-argument constructor. The superclass’s constructor is called at the beginning of the constructor body.
  • Invoking a non-default superclass constructor:
    1. initializer list
    2. superclass’s no-arg constructor
    3. main class’s no-arg constructor
  • If the superclass doesn’t have an unnamed, no-argument constructor, then you must manually call one of the constructors in the superclass. Specify the superclass constructor after a colon (:), just before the constructor body (if any)
  • The arguments to the superclass constructor are evaluated before invoking the constructor.
  • Arguments to the superclass constructor do not have access to this. For example, arguments can call static methods but not instance methods.
  • Initializer list: 除了调用父类的构造函数之外,使用 initializer list 可以在运行 constructor body 之前初始化实例变量。
  • During development, you can validate inputs by using assert in the initializer list.
  • Initializer lists are handy when setting up final fields.
  • A redirecting constructor’s body is empty.
  • Constant constructors: If your class produces objects that never change, you can make these objects compile-time constants. To do this, define a const constructor and make sure that all instance variables are final.
  • Factory constructors: 使用 factory 来修饰 constructor,这种构造函数可以从 cache 中返回实例或者返回 subtype 的实例,而==不会总是返回新创建==的实例。Factory constructors have no access to this.

Methods

  • Getters and setters 使用 get and set keywords,这跟 Java 有所不同,跟 Kotlin 类似
  • Abstract methods: 1. only exist in abstract classes; 2. use a semicolon (;) instead of a method body.
  • 工具类可以使用 ==top-level== 机制的优势来建立

Abstract classes

  • Abstract classes can’t be instantiated.
  • Useful for defining interfaces, often with some implementation.

Implicit interface

Every class implicitly defines an interface containing all the instance members of the class and of any interfaces it implements. A class implements one or more interfaces by declaring them in an implements clause and then providing the APIs required by the interfaces.

在 Dart 中没有单纯的 interface (即像其他语言一样使用 interface 关键字修饰的类),它令每个类都具有隐式 interface 的功能(即每个类都可以被 implemented)。Implement 了一个类之后可以使用它的 instance member(变量和方法)

Extending a class

Use extends to create a subclass, and super to refer to the superclass.

  • Overridable operators: 可以在类的内部对运算符的功能进行重新,比如 +- 等。
  • noSuchMethod(): 这个方法用来处理:当程序想要调用一个类并未定义的方法时,这个方法就会被调用,可以在这个方法里面编写自己所需要的逻辑。这个方法用于调试目的。

Enumerated types

  • Each value in an enum has a index getter, zero-based of the value in the enum declaration.
  • To get a list of all the values in the enum, use the enum’s values constant.

Singleton

  • 可以使用 Factory constructors , 参考这里
  • 直接使用 ==top-level== variables 的天然优势

Mixins

Mixins are a way of reusing a class’s code in multiple class hierarchies.

  • To use a mixin, use the with keyword followed by one or more mixin names.

Static variables & methods

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

  • Static variables aren’t initialized until they’re used.
  • Using ==lowerCamelCase== for constant names follow by style guide recommendation.
  • Consider using top-level functions, instead of static methods, for common or widely used utilities and functionality.
  • You can use static methods as compile-time constants. For example, you can pass a static method as a parameter to a constant constructor.

Exception

includes Throw, Catch and Finally

  • In contrast to Java, all of Dart’s exceptions are unchecked exceptions. Methods do not declare which exceptions they might throw, and you are not required to catch any exceptions

Catch

  • You can use either on or catch or ==both==. Use on when you need to specify the exception type. Use catch when your exception handler needs the exception object.
  • catch() can have one or two parameters. The first is the exception that was thrown, and the second is the stack trace.
  • To partially handle an exception, while allowing it to propagate, use the rethrow.

Libraries and visibility

Using libraries

For built-in libraries, the URI has the special dart: scheme. For other libraries, you can use a file system path or the package: scheme. The package: scheme specifies libraries provided by a package manager such as the ==pub tool==.

Specifying a library prefix

If you import two libraries that have conflicting identifiers, then you can specify a prefix for one or both libraries.

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

Element ele1 = Element();
lib2.Element ele2 = lib2.Element();

Importing partly

If you want to use only part of a library, you can selectively import the library. ( Tree-shaking can avoid this automatically )

// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;

Lazily loading (Deffered loading)

Load a library on demand, if and when it’s needed.

Use cases:

  • To reduce an app’s initial startup time.
  • To perform A/B testing—trying out alternative implementations of an algorithm, for example.
  • To load rarely used functionality, such as optional screens and dialogs.
// To lazily load a library, using `deferred as`
import 'package:greetings/hello.dart' deferred as hello;

// When you need the library, invoke loadLibrary() using the library's identifier
Future greet() async {
    await hello.loadLibrary();
    hello.printGreeting();
}

Keep in mind the following when you use deferred loading:

  • You can invoke loadLibrary() multiple times on a library without problems. The library is loaded only once.
  • A deferred library’s constants aren’t constants in the importing file. Remember, these constants don’t exist until the deferred library is loaded.
  • You can’t use types from a deferred library in the importing file. Instead, consider moving interface types to a library imported by both the deferred library and the importing file.
  • Dart implicitly inserts loadLibrary() into the namespace that you define using deferred as namespace. The loadLibrary() function returns a ==Future==.

如何创建自己的 library package

How-to

Asynchrony

Code that uses async and await is asynchronous.

  • To use await , code must be in an async function - a function marked as async

    Future checkVersion() async {
        var version = await lookUpVersion();
    }
    
  • Before you directly use the Future API, consider using await instead. Code that uses await expressions can be easier to understand than code that uses the Future API.

  • In await <expression>, the value of expression is usually a ==Future==; if it isn’t, then the value is automatically wrapped in a Future. The await expression makes execution pause until that object is available.

  • Although an async function might perform time-consuming operations, it doesn’t wait for those operations. Instead, the async function executes only until it encounters its first await expression. Then it returns a Future object, resuming execution only after the await expression completes.

Future API vs Await

  • If the callback registered with then() returns a Future, then() returns an ==equivalent Future==. If the callback returns a value of any other type, then() ==creates a new Future== that completes with the value.
// Use future api
Future result = costlyQuery(url);
result.then((value) => expensiveWork(value))
    .then((_) => lengthyComputation())
    .then((_) => print('Done!'))
    .catchError((exception) {
        // then().catchError() pattern is the asynchronous version of try-catch
        /* Handle exception... */
    });

// Use await
try {
    final value = await costlyQuery(url);
    await expensiveWork(value);
    await lengthyComputation();
    print('Done!');
} catch (e) {
    /* Handler exception... */
}
  • Version note: In Dart 1.x, async functions immediately suspended execution. In Dart 2, instead of immediately suspending, async functions execute synchronously until the first await or return.
  • By convention, an unused argument is named _ (undersocre).

Waiting for multiple futures

Use the Future.wait() static method to manage multiple Futures and wait for them to complete before continuing.

Future deleteLotsOfFiles() async => ...;
Future copyLotsOfFiles() async => ...;
Future checksumLotsOfOtherFiles() async => ...;
    
await Future.wait([
    deleteLotsOfFiles(),
    copyLotsOfFiles(),
    checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');