Dart Tour

接口和抽象类

Dart has no interface keyword. Instead, all classes implicitly define an interface. Therefore, you can implement any class.

class MockSpaceship implements Spacecraft {
  // ···
}

You can create an abstract class to be extended (or implemented) by a concrete class. Abstract classes can contain abstract methods (with empty bodies).

abstract class Describable {
  void describe();

  void describeWithEmphasis() {
    print('=========');
    describe();
    print('=========');
  }
}

Any class extending Describable has the describeWithEmphasis() method, which calls the extender’s implementation of describe().

Dart doesn’t have keywords for public, private, or protected.

PREFER using lowerCamelCase for constant names.

In new code, use lowerCamelCase for constant variables, including enum values.

const pi = 3.14;
const defaultTimeout = 1000;
final urlScheme = RegExp('^([a-z]+):');

class Dice {
  static final numberGenerator = Random();
}
const PI = 3.14;
const DefaultTimeout = 1000;
final URL_SCHEME = RegExp('^([a-z]+):');

class Dice {
  static final NUMBER_GENERATOR = Random();
}

You may use SCREAMING_CAPS for consistency with existing code, as in the following cases:

  • When adding code to a file or library that already uses
    SCREAMING_CAPS.
  • When generating Dart code that’s parallel to Java code — for example,
    in enumerated types generated from protobufs.

Note: We initially used Java’s SCREAMING_CAPS style for constants. We changed for a few reasons:

  • SCREAMING_CAPS looks bad for many cases, particularly enum values for
    things like CSS colors.
  • Constants are often changed to final non-const variables, which would
    necessitate a name change.
  • The values property automatically defined on an enum type is const
    and lowercase.

Null-aware operators

Dart offers some handy operators for dealing with values that might be null. One is the ??= assignment operator, which assigns a value to a variable only if that variable is currently null:

int a; // The initial value of a is null.

a ??= 3;
print(a); // <-- Prints 3.

a ??= 5;
print(a); // <-- Still prints 3.

Another null-aware operator is ??, which returns the expression on its left unless that expression’s value is null, in which case it evaluates and returns the expression on its right:

print(1 ?? 3); // <-- Prints 1.
print(null ?? 12); // <-- Prints 12.

Conditional property access

To guard access to a property or method of an object that might be null, put a question mark (?) before the dot (.):

myObject?.someProperty

The preceding code is equivalent to the following:

(myObject != null) ? myObject.someProperty : null

You can chain multiple uses of ?. together in a single expression:

myObject?.someProperty?.someMethod()

The preceding code returns null (and never calls someMethod()) if either myObject or myObject.someProperty is null.

Collection literals

Dart has built-in support for lists, maps, and sets. You can create them using literals:

final aListOfStrings = ['one', 'two', 'three'];
final aSetOfStrings = {'one', 'two', 'three'};
final aMapOfStringsToInts = {
  'one': 1,
  'two': 2,
  'three': 3,
};

Cascades

To perform a sequence of operations on the same object, use cascades (..). We’ve all seen an expression like this:

myObject.someMethod()

It invokes someMethod() on myObject, and the result of the expression is the return value of someMethod().

Here’s the same expression with a cascade:

myObject..someMethod()

Although it still invokes someMethod() on myObject, the result of the expression isn’t the return value — it’s a reference to myObject! Using cascades, you can chain together operations that would otherwise require separate statements. For example, consider this code:

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

With cascades, the code becomes much shorter, and you don’t need the button variable:

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

Initializer lists

Sometimes when you implement a constructor, you need to do some setup before the constructor body executes. For example, final fields must have values before the constructor body executes. Do this work in an initializer list, which goes between the constructor’s signature and its body:

Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

The initializer list is also a handy place to put asserts, which run only during development:

NonNegativePoint(this.x, this.y)
    : assert(x >= 0),
      assert(y >= 0) {
  print('I just made a NonNegativePoint: ($x, $y)');
}

Named constructors

To allow classes to have multiple constructors, Dart supports named constructors:

class Point {
  num x, y;

  Point(this.x, this.y);

  Point.origin() {
    x = 0;
    y = 0;
  }
}

To use a named constructor, invoke it using its full name:

final myPoint = Point.origin();

Factory constructors

Dart supports factory constructors, which can return subtypes or even null. To create a factory constructor, use the factory keyword:

class Square extends Shape {}

class Circle extends Shape {}

class Shape {
  Shape();

  factory Shape.fromTypeName(String typeName) {
    if (typeName == 'square') return Square();
    if (typeName == 'circle') return Circle();

    print('I don\'t recognize $typeName');
    return null;
  }
}

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

class ImmutablePoint {
  const ImmutablePoint(this.x, this.y);

  final int x;
  final int y;

  static const ImmutablePoint origin = ImmutablePoint(0, 0);
}

any() and every() 区别

The Iterable class provides two methods that you can use to verify conditions:

  • any(): Returns true if at least one element satisfies the condition.
  • every(): Returns true if all elements satisfy the condition.

Future

A Future<T> instance produces a value of type T.

  • If a future doesn’t produce a usable value, then the future’s type is Future<void>.
  • A future can be in one of two states: uncompleted or completed.
  • When you call a function that returns a future, the function queues up work to be done and returns an uncompleted future.
  • When a future’s operation finishes, the future completes with a value or with an error.

dart packages依赖版本

Although semantic versioning doesn’t promise any compatibility between versions prior to 1.0.0, the Dart community convention is to treat those versions semantically as well. The interpretation of each number is just shifted down one slot: going from 0.1.2 to 0.2.0 indicates a breaking change, going to 0.1.3 indicates a new feature, and going to 0.1.2+1 indicates a change that doesn’t affect the public API.

请在doc注释中使用方括号代指标识符。

If you surround things like variable, method, or type names in square brackets, then dartdoc looks up the name and links to the relevant API docs. Parentheses are optional, but can make it clearer when you’re referring to a method or constructor.

/// Throws a [StateError] if ...
/// similar to [anotherMethod()], but ...
To link to a member of a specific class, use the class name and member name, separated by a dot:

/// Similar to [Duration.inDays], but handles fractional days.
The dot syntax can also be used to refer to named constructors. For the unnamed constructor, put parentheses after the class name:

/// To create a point, call [Point()] or use [Point.polar()] to ...

DO use whereType() to filter a collection by type.

var objects = [1, "a", 2, "b", 3];
var ints = objects.whereType<int>();

DON’T use cast() when a nearby operation will do.

图片描述

AVOID using cast().

This is the softer generalization of the previous rule. Sometimes there is no nearby operation you can use to fix the type of some object. Even then, when possible avoid using cast() to “change” a collection’s type.

Prefer any of these options instead:

  • Create it with the right type. Change the code where the collection
    is first created so that it has the right type.
  • Cast the elements on access. If you immediately iterate over the
    collection, cast each element inside the iteration.
  • Eagerly cast using List.from(). If you’ll eventually access most of
    the elements in the collection, and you don’t need the object to be
    backed by the original live object, convert it using List.from().

The cast() method returns a lazy collection that checks the element type on every operation. If you perform only a few operations on only a few elements, that laziness can be good. But in many cases, the overhead of lazy validation and of wrapping outweighs the benefits.

Here is an example of creating it with the right type:
在这里插入图片描述

DON’T create a lambda when a tear-off will do.

If you refer to a method on an object but omit the parentheses, Dart gives you a “tear-off”—a closure that takes the same parameters as the method and invokes it when you call it.

If you have a function that invokes a method with the same arguments as are passed to it, you don’t need to manually wrap the call in a lambda.

//good
names.forEach(print);
//bad
names.forEach((name) {
  print(name);
});

AVOID implementing a class that isn’t intended to be an interface.

Implicit interfaces are a powerful tool in Dart to avoid having to repeat the contract of a class when it can be trivially inferred from the signatures of an implementation of that contract.

But implementing a class’s interface is a very tight coupling to that class. It means virtually any change to the class whose interface you are implementing will break your implementation. For example, adding a new member to a class is usually a safe, non-breaking change. But if you are implementing that class’s interface, now your class has a static error because it lacks an implementation of that new method.

Library maintainers need the ability to evolve existing classes without breaking users. If you treat every class like it exposes an interface that users are free to implement, then changing those classes becomes very difficult. That difficulty in turn means the libraries you rely on are slower to grow and adapt to new needs.

To give the authors of the classes you use more leeway, avoid implementing implicit interfaces except for classes that are clearly intended to be implemented. Otherwise, you may introduce a coupling that the author doesn’t intend, and they may break your code without realizing it.

DO document if your class supports being used as an interface.

If your class can be used as an interface, mention that in the class’s doc comment.

AVOID mixing in a type that isn’t intended to be a mixin.

For compatibility, Dart still allows you to mix in classes that aren’t defined using mixin. However, that’s risky. If the author of the class doesn’t intend the class to be used as a mixin, they might change the class in a way that breaks the mixin restrictions. For example, if they add a constructor, your class will break.

If the class doesn’t have a doc comment or an obvious name like IterableMixin, assume you cannot mix in the class if it isn’t declared using mixin.

DO use mixin to define a mixin type.

Dart originally didn’t have a separate syntax for declaring a class intended to be mixed in to other classes. Instead, any class that met certain restrictions (no non-default constructor, no superclass, etc.) could be used as a mixin. This was confusing because the author of the class might not have intended it to be mixed in.

Dart 2.1.0 added a mixin keyword for explicitly declaring a mixin. Types created using that can only be used as mixins, and the language also ensures that your mixin stays within the restrictions. When defining a new type that you intend to be used as a mixin, use this syntax.

mixin ClickableMixin implements Control {
  bool _isDown = false;

  void click();

  void mouseDown() {
    _isDown = true;
  }

  void mouseUp() {
    if (_isDown) click();
    _isDown = false;
  }
}

You might still encounter older code using class to define mixins, but the new syntax is preferred.

external 关键词

The body of the function is defined somewhere else.
As far as I know this is used to fix different implementations for Dart VM in the browser and Dart VM on the Server.

DO annotate with Object instead of dynamic to indicate any object is allowed.

Some operations work with any possible object. For example, a log() method could take any object and call toString() on it. Two types in Dart permit all values: Object and dynamic. However, they convey different things. If you simply want to state that you allow all objects, use Object, as you would in Java or C#.

Using dynamic sends a more complex signal. It may mean that Dart’s type system isn’t sophisticated enough to represent the set of types that are allowed, or that the values are coming from interop or otherwise outside of the purview of the static type system, or that you explicitly want runtime dynamism at that point in the program.

void log(Object object) {
  print(object.toString());
}

/// Returns a Boolean representation for [arg], which must
/// be a String or bool.
 

What’s the difference between async and async* in Dart?

Marking a function as async or async* allows it to use async/await keyword to use a Future.

The difference between both is that async* will always returns a Stream and offer some syntax sugar to emit a value through yield keyword.

We can therefore do the following:

Stream<int> foo() async* {
  for (int i = 0; i < 42; i++) {
    await Future.delayed(const Duration(seconds: 1));
    yield i;
  }
}

This function emits a value every second, that increment every time

AVOID optional positional parameters if the user may want to omit earlier parameters.

Optional positional parameters should have a logical progression such that earlier parameters are passed more often than later ones. Users should almost never need to explicitly pass a “hole” to omit an earlier positional argument to pass later one. You’re better off using named arguments for that.

show hide用法

见API Conflicts

API conflicts

If an extension member conflicts with an interface or with another extension member, then you have a few options.

One option is changing how you import the conflicting extension, using show or hide to limit the exposed API:

// Defines the String extension method parseInt().
import 'string_apis.dart';

// Also defines parseInt(), but hiding NumberParsing2
// hides that extension method.
import 'string_apis_2.dart' hide NumberParsing2;

// ···
// Uses the parseInt() defined in 'string_apis.dart'.
print('42'.parseInt());
Another option is applying the extension explicitly, which results in code that looks as if the extension is a wrapper class:

// Both libraries define extensions on String that contain parseInt(),
// and the extensions have different names.
import 'string_apis.dart'; // Contains NumberParsing extension.
import 'string_apis_2.dart'; // Contains NumberParsing2 extension.

// ···
// print('42'.parseInt()); // Doesn't work.
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());
If both extensions have the same name, then you might need to import using a prefix:

// Both libraries define extensions named NumberParsing
// that contain the extension method parseInt(). One NumberParsing
// extension (in 'string_apis_3.dart') also defines parseNum().
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;

// ···
// print('42'.parseInt()); // Doesn't work.

// Use the ParseNumbers extension from string_apis.dart.
print(NumberParsing('42').parseInt());

// Use the ParseNumbers extension from string_apis_3.dart.
print(rad.NumberParsing('42').parseInt());

// Only string_apis_3.dart has parseNum().
print('42'.parseNum());

As the example shows, you can invoke extension methods implicitly even if you import using a prefix. The only time you need to use the prefix is to avoid a name conflict when invoking an extension explicitly.

Implicit interfaces

Every class implicitly defines an interface containing all the instance members of the class and of any interfaces it implements. If you want to create a class A that supports class B’s API without inheriting B’s implementation, class A should implement the B interface.

A class implements one or more interfaces by declaring them in an implements clause and then providing the APIs required by the interfaces. For example:

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final _name;

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

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

// An 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()));
}

Here’s an example of specifying that a class implements multiple interfaces:

class Point implements Comparable, Location {...}

Isolates

await for 关键词:Difference between await for and listen in Dart

yield和yield*区别

async* VS StreamController

Avoid producing events when the listener has requested a pause. An async* function automatically pauses at a yield statement while the stream subscription is paused.
As a rule, streams should wait for subscribers before starting their work. An async* function does this automatically, but when using a StreamController, you are in full control and can add events even when you shouldn’t.

StreamController使用建议

When creating a stream without using an async* function, keep these tips in mind:

  • Be careful when using a synchronous controller—for example, one created using StreamController(sync: true). When you send an event on an unpaused synchronous controller (for example, using the add(), addError(), or close() methods defined by EventSink), the event is sent immediately to all listeners on the stream. Stream listeners must never be called until the code that added the listener has fully returned, and using a synchronous controller at the wrong time can break this promise and cause good code to fail. Avoid using synchronous controllers.
  • If you use StreamController, the onListen callback is called before the listen call returns the StreamSubscription. Don’t let the onListen callback depend on the subscription already existing. For example, in the following code, an onListen event fires (and handler is called) before the subscription variable has a valid value.
subscription = stream.listen(handler);
  • The onListen, onPause, onResume, and onCancel callbacks defined by StreamController are called by the stream when the stream’s listener state changes, but never during the firing of an event or during the call of another state change handler. In those cases, the state change callback is delayed until the previous callback is complete.
  • Don’t try to implement the Stream interface yourself. It’s easy to get the interaction between events, callbacks, and adding and removing listeners subtly wrong. Always use an existing stream, possibly from a StreamController, to implement the listen call of a new stream.
  • Although it’s possible to create classes that extend Stream with more functionality by extending the Stream class and implementing the listen method and the extra functionality on top, that is generally not recommended because it introduces a new type that users have to consider. Instead you can often make a class that has a Stream (and more) — instead of one that is a Stream (and more).

Conditionally importing and exporting library files 有条件的倒入、导出

If your library supports multiple platforms, then you might need to conditionally import or export library files. A common use case is a library that supports both web and native platforms.

To conditionally import or export, you need to check for the presence of dart:* libraries. Here’s an example of conditional export code that checks for the presence of dart:io and dart:html:

export 'src/hw_none.dart' // Stub implementation
    if (dart.library.io) 'src/hw_io.dart' // dart:io implementation
    if (dart.library.html) 'src/hw_html.dart'; // dart:html implementation
lib/hw_mp.dart

Here’s what that code does:

In an app that can use dart:io (for example, a command-line app), export src/hw_io.dart.
In an app that can use dart:html (a web app), export src/hw_html.dart.
Otherwise, export src/hw_none.dart.
To conditionally import a file, use the same code as above, but change export to import.

Note: The conditional import or export checks only whether the
library is available for use on the current platform, not whether it’s
actually imported or used.

All of the conditionally exported libraries must implement the same API. For example, here’s the dart:io implementation:

import 'dart:io';

void alarm([String text]) {
  stderr.writeln(text ?? message);
}

String get message => 'Hello World from the VM!';
lib/src/hw_io.dart
And here’s the default implementation, which is a stub that throws UnsupportedErrors:

void alarm([String text]) => throw UnsupportedError('hw_none alarm');

String get message => throw UnsupportedError('hw_none message');
lib/src/hw_none.dart

On any platform, you can import the library that has the conditional export code:

import 'package:hw_mp/hw_mp.dart';

void main() {
  print(message);
}

查看flutter依赖关系

To see all the dependencies used by a package, use pub deps.

package根据git依赖的话,是可以指定文件夹的

Pub assumes that the package is in the root of the Git repository. To specify a different location in the repo, use the path argument:

dependencies:
  kittens:
    git:
      url: git://github.com/munificent/cats.git
      path: path/to/kittens

The path is relative to the Git repo’s root.

Path packages

Sometimes you find yourself working on multiple related packages at the same time. Maybe you are creating a framework while building an app that uses it. In those cases, during development you really want to depend on the live version of that package on your local file system. That way changes in one package are instantly picked up by the one that depends on it.

To handle that, pub supports path dependencies.

dependencies:
  transmogrify:
    path: /Users/me/transmogrify

This says the root directory for transmogrify is /Users/me/transmogrify. For this dependency, pub generates a symlink directly to the lib directory of the referenced package directory. Any changes you make to the dependent package are seen immediately. You don’t need to run pub every time you change the dependent package.

Relative paths are allowed and are considered relative to the directory containing your pubspec.

Path dependencies are useful for local development, but do not work when sharing code with the outside world—not everyone can get to your file system. Because of this, you cannot upload a package to the pub.dev site if it has any path dependencies in its pubspec.

Instead, the typical workflow is:

  • Edit your pubspec locally to use a path dependency.
  • Work on the main package and the package it depends on.
  • Once they’re both working, publish the dependent package.
  • Change your pubspec to point to the now hosted version of its
    dependent.
  • Publish your main package too, if you want.

Version constraints

If your package is an application, you don’t usually need to specify version constraints for your dependencies. You typically want to use the latest versions of the dependencies when you first create your app. Then you’ll create and check in a lockfile that pins your dependencies to those specific versions. Specifying version constraints in your pubspec then is usually redundant (though you can do it if you want).

For a library package that you want users to reuse, though, it is important to specify version constraints. That lets people using your package know which versions of its dependencies they can rely on to be compatible with your library. Your goal is to allow a range of versions as wide as possible to give your users flexibility. But it should be narrow enough to exclude versions that you know don’t work or haven’t been tested.

The Dart community uses semantic versioning1, which helps you know which versions should work. If you know that your package works fine with 1.2.3 of some dependency, then semantic versioning tells you that it should work (at least) up to 2.0.0.

A version constraint is a series of:

a. any

The string any allows any version. This is equivalent to an empty version constraint, but is more explicit. While any is allowed, we do not recommend it for performance reasons.

b. 1.2.3

A concrete version number pins the dependency to only allow that exact version. Avoid using this when you can because it can cause version lock for your users and make it hard for them to use your package along with other packages that also depend on it.

c. >=1.2.3

Allows the given version or any greater one. You’ll typically use this.

d. 1.2.3

Allows any version greater than the specified one but not that version itself.

e. <=1.2.3

Allows any version lower than or equal to the specified one. You won’t typically use this.
f. <1.2.3
Allows any version lower than the specified one but not that version itself. This is what you’ll usually use because it lets you specify the upper version that you know does not work with your package (because it’s the first version to introduce some breaking change).
You can specify version parts as you want, and their ranges are intersected together. For example, '>=1.2.3 <2.0.0' allows any version from 1.2.3 to 2.0.0 excluding 2.0.0 itself. An easier way to express this range is by using caret syntax, or ^1.2.3

If the > character is in the version constraint, be sure to quote the
constraint string
, so the character isn’t interpreted as YAML syntax.
For example, never use >=1.2.3 <2.0.0; instead, use '>=1.2.3 <2.0.0'
or ^1.2.3.

Caret syntax(即^)

Caret syntax provides a more compact way of expressing the most common sort of version constraint. ^version means “the range of all versions guaranteed to be backwards compatible with the specified version”, and follows pub’s convention for semantic versioning. For example, ^1.2.3 is equivalent to '>=1.2.3 <2.0.0', and ^0.1.2 is equivalent to '>=0.1.2 <0.2.0'. The following is an example of caret syntax:

dependencies:
  path: ^1.3.0
  collection: ^1.1.0
  string_scanner: ^0.1.2

Note that caret syntax was added in Dart 1.8.3. Older versions of Dart don’t understand it, so you’ll need to include an SDK constraint (using traditional syntax) to ensure that older versions of pub will not try to process it. For example:

environment:
  sdk: '>=1.8.3 <3.0.0'

Dev dependencies(不会被宿主引入)

Pub supports two flavors of dependencies: regular dependencies and dev dependencies. Dev dependencies differ from regular dependencies in that dev dependencies of packages you depend on are ignored. Here’s an example:

Say the transmogrify package uses the test package in its tests and only in its tests. If someone just wants to use transmogrify—import its libraries—it doesn’t actually need test. In this case, it specifies test as a dev dependency. Its pubspec will have something like:

dev_dependencies:
  test: '>=0.5.0 <0.12.0'

Pub gets every package that your package depends on, and everything those packages depend on, transitively. It also gets your package’s dev dependencies, but it ignores the dev dependencies of any dependent packages. Pub only gets your package’s dev dependencies. So when your package depends on transmogrify it will get transmogrify but not test.

Dependency overrides

You can use dependency_overrides to temporarily override all references to a dependency.

For example, perhaps you are updating a local copy of transmogrify, a published library package. Transmogrify is used by other packages in your dependency graph, but you don’t want to clone each package locally and change each pubspec to test your local copy of transmogrify.

In this situation, you can override the dependency using dependency_overrides to specify the directory holding the local copy of the package.

The pubspec would look something like the following:

name: my_app
dependencies:
  transmogrify: ^1.2.0
dependency_overrides:
  transmogrify:
    path: ../transmogrify_patch/

When you run pub get, the pubspec’s lockfile is updated to reflect the new path to your dependency and, whereever transmogrify is used, pub uses the local version instead.

You can also use dependency_overrides to specify a particular version of a package:

name: my_app
dependencies:
  transmogrify: ^1.2.0
dependency_overrides:
  transmogrify: '3.2.1'

Caution: Using a dependency override involves some risk. For example, using an override to specify a version outside the range that the package claims to support, or using an override to specify a local copy of a package that has unexpected behaviors, may break your application.

Entrypoint

In the general context of Dart, an entrypoint is a Dart library that is directly invoked by a Dart implementation. When you reference a Dart library in a <script> tag or pass it as a command-line argument to the standalone Dart VM, that library is the entrypoint. In other words, it’s usually the .dart file that contains main().

In the context of pub, an entrypoint package or root package is the root of a dependency graph. It will usually be an application. When you run your app, it’s the entrypoint package. Every other package it depends on will not be an entrypoint in that context.

A package can be an entrypoint in some contexts and not in others. Say your app uses a library package A. When you run your app, A is not the entrypoint package. However, if you go over to A and execute its tests, in that context, it is the entrypoint since your app isn’t involved.

Entrypoint directory

A directory inside your package that is allowed to contain Dart entrypoints.

Pub has a whitelist of these directories: benchmark, bin, example, test, tool, and web. Any subdirectories of those (except bin) may also contain entrypoints.

Library package

A package that other packages can depend on. Library packages can have dependencies on other packages and can be dependencies themselves. They can also include scripts to be run directly. The opposite of a library package is an application package.

Don’t check the lockfile of a library package into source control, since libraries should support a range of dependency versions. The version constraints of a library package’s immediate dependencies should be as wide as possible while still ensuring that the dependencies will be compatible with the versions that were tested against.

Application package

A package that is not intended to be used as a library. Application packages may have dependencies on other packages, but are never depended on themselves. They are usually meant to be run directly, either on the command line or in a browser. The opposite of an application package is a library package.

Application packages should check their lockfiles into source control, so that everyone working on the application and every location the application is deployed has a consistent set of dependencies. Because their dependencies are constrained by the lockfile, application packages usually specify any for their dependencies’ version constraints.

Lockfile

A file named pubspec.lock that specifies the concrete versions and other identifying information for every immediate and transitive dependency a package relies on.

Unlike the pubspec, which only lists immediate dependencies and allows version ranges, the lock file comprehensively pins down the entire dependency graph to specific versions of packages. A lockfile ensures that you can recreate the exact configuration of packages used by an application.

The lockfile is generated automatically for you by pub when you run pub get, pub upgrade, or pub downgrade. If your package is an application package, you will typically check this into source control. For library packages, you usually won’t.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值