How do Dart Package Versions work & how should I version my Flutter Plugins?

Issue

I am wondering how Dart package versions are resolved in my Flutter application.

Say, I have a dependency foo and declare a dependency like this:

dependencies:
  foo: ^1.2.3

How does Dart Pub know what version to resolve / what happens when a new version is available?


Furthermore, if I want to publish my Flutter plugin to pub.dev, how do I decide when to increase what part of the version?

Solution

Dart package versioning

All of this goes back to Dart package versioning, i.e. there is no distinction for Flutter plugins.

It is based on SemVer 2.0.0-rc.1 (Semantic Versioning). For Dart packages, the gist of the covention is as follows:

<1.0.0 >=1.0.0
0.major.minor+patch major.minor.patch

Note that Pub also supports pre-release versions denoted by:

<1.0.0 >=1.0.0
0.major.0-prerelease.patch major.0.0-prerelease.patch

Example versions might be 0.6.0-nullsafety.0 (with a patch version of 0.6.0-nullsafety.1) or 2.0.0-dev.1.

Learn more about Dart’s package versioning.

Resolving versions

To get a basic understanding of what problem the version resolving tries to solve, you can read through the Resolving shared dependencies section of Dart’s package versioning article. The aspect of version resolving that I will look at here is the caret syntax.

Caret ^ syntax is the standard syntax for resolving versions in Dart and closely connected to the versioning tables above. Whenever you define a dependency using foo: ^1.0.0, there is a bit of logic involved to determine which versions can be resolved from that string.

In general, ^ matches up to major versions. This means that whenever you have a breaking change in your API, you will want to bump the major version because then dependants will not automatically be upgraded to your next package version. I will try to illustrate the matching in a table again:

^0.4.2+1 ^1.3.0
>=0.4.2+1 <0.5.0 >=1.3.0 <2.0.0

As you can see, caret syntax will match any version that is greater than or equal to the current version but smaller than the next major version.

Note that pre-release versions are handled differently than normal (not exactly according to the SemVer spec) in Dart. You can find the source code for Pub’s pub_semver tool on GitHub. It declares that pre-release versions are not matched by the caret syntax (and neither by the traditional syntax):

^1.0.0 matches version?
1.4.2 yes
1.5.0-beta yes
2.0.0-alpha no
2.0.0 no

Learn more about Dart’s package dependencies.

pubspec.lock

I quickly want to mention the role of the Pubspec lock file for dependency resolving in Dart.

There is a straight-forward way that I think defines how package versions are fetched:

  • pub get without an exiting pubspec.lock (initial fetch) will fetch the latest possible versions (according to above rules and satisfying all direct and transitive constraints of shared dependencies).
  • pub get with an existing pubspec.lock file will prefer the locked versions in the lock file over latest versions (if they still satisfy constraints by potentially newly introduced dependencies since the last fetch).
  • pub upgrade throws away the existing pubspec.lock file and then acts the same way as the initial pub get.

Consequently, running pub get in your app will not unexpectedly fetch new versions. And when working with colleagues, everyone will fetch the same versions according to the pubspec.lock file when running pub get and having included the lock file in version control.

Learn more about lockfiles in Dart.

Best practices

Because the Dart team recommends some best practices for versioning, I want to make sure to include them in here directly:

Use caret syntax
Specifying dependencies with version ranges is such as ^1.6.3 is a good practice because it allows the pub tool to select newer versions of the package when they become available. Also, it places an upper limit on the allowed version, based on an assumption that packages use semantic versions, where any version of path versioned 1.x is compatible, but where a new version 2.x would be a major upgrade that isn’t semantically compatible with 1.x versions.

Depend on the latest stable package versions
Use pub upgrade to update to the latest package versions that your pubspec allows. To identify dependencies in your app or package that aren’t on the latest stable versions, use pub outdated.

Test whenever you update package dependencies
If you run pub upgrade without updating your pubspec, the API should stay the same and your code should run as before — but test to make sure. If you modify the pubspec and update to a new major version, then you might encounter breaking changes, so you need to test even more thoroughly.

Answered By – creativecreatorormaybenot

Answer Checked By – Robin (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *