[MUSIC PLAYING]
JOHN RYAN: Hi, I'm John from the Dart and Flutter team.
In this session, we'll cover everything
you need to know about building open source Dart and Flutter
packages.
Almost every Flutter app is built
using open source Dart packages hosted on pub.dev, Dart's
official package repository.
When you publish a Dart package, it opens up new opportunities
to collaborate with developers from all over the world.
In this talk, we'll see how to use Dart's tools
to keep your Dart packages squeaky clean and easy
to maintain to help you and other open source collaborators
ship grade code.
We'll see how to get the most out of pub,
Dart's package manager, how to set up continuous integration,
and finally, show how to publish.
When developers work together, everyone
gets a higher return on their investment.
That's why the Dart and Flutter SDKs
have been open source from the very beginning.
Let's start by creating a package.
I'm starting with a Flutter app that
takes a list of tasks and schedules them on my calendar.
Instead of publishing my entire app,
I'd like to just publish the constraint solving algorithm
as a standalone package.
To create a new Dart package, run the Dart create command.
If your package depends on the Flutter SDK,
use the Flutter create command instead.
In this case, I'll create a Dart package
so that developers can use it without the Flutter
SDK, such as on a development machine or a server.
A Dart package has a file structure like this.
The Lib directory is for code that other developers will use
when importing your package.
The test directory is for writing tests for your package.
And the example directory should contain examples
of how to use your package.
The pub spec file contains important information
about your package for pub.dev, including
its version and dependencies.
If you're using Dart 3 features, such as patterns or records,
set the minimum SDK version so that developers
that depend on your package will only get this version
when using the Dart 3 SDK.
Once everything is configured, run Dart pub
get to fetch the dependencies in your pub spec.
To verify that your package is working,
make sure to write some tests and run
them using the Dart test command or run the example.
Now that we're up and running, we
might need our package to depend on other packages
so we don't have to write everything from scratch.
Let's ship our package over to Jonas for some help.
JONAS JENSEN: Hi, I'm Jonas.
I work on package delivery, not this kind
of packages, but as a software engineer on pub.dev
delivering Dart and Flutter packages.
And I'm going to talk about how you can
manage package dependencies.
Before we dive deep into dependencies,
we need a quick note on package version numbers
And how version numbers relate to each other.
Dart uses semantic versioning, also known as Sember,
where each version number is made up
of a major, minor, and patch number separated by dots.
And you normally start from version 1.0.0
as the first stable version.
When publishing new versions of a package,
you increase the major version when doing a breaking change
in the API.
For example, you might be removing a function or a class,
or completely changing the behavior
of a function of a class.
If you're only adding new functionality,
you do a minor version increase.
For example, you might be adding a class or a function.
And if you're only fixing bugs and not changing the API,
you can set on increasing the patched version.
Using major, minor patch numbers allows
users of a package to quickly assess
the size and the scope of the changes in a new version.
Obviously, we cannot encode everything in the version
number, so it's customary to write a changelog file that
describes what's changed in each version.
Back to dependencies, packages can depend on other packages.
When adding dependency on a package,
we usually write a constraint on the version number
of the package in the pubspec.yaml.
We can do this using the Dart pub add command, which takes
a package name as an argument.
For example, to add the path package,
we can run this command.
This automatically finds the latest compatible version
of path, adds path to your pubspec.yaml
and writes a constraint on the version number of the path
package.
It usually writes a carrot constraint.
The carrot constraint is a shorthand
for at least this version and less
than the next major version, which
is a quick way of saying this package needs
version 1.8.3 or newer, but don't give us
a new major version.
Since breaking changes require a new major version,
it should be safe to use any newer version up
to the next major version.
When we fetch dependencies, we'll
have a pubspec.lock file, which locks dependencies
to a specific version.
If you're writing an application,
it's important to check the lock file into source control
because the lock file ensures that the version
of your dependency that you're using in testing
is also the version used in production.
And while I said it should be safe to use a newer version up
to the next major version, that's not always the case
because newer versions of a package
may have new or different bugs.
So an app developer should always test their app
using the exact version of the packages
that they plan to use when releasing the app.
However, if you're creating a package
to be published on pub.dev, it's often
best to not check in the lock file
because you will want to be testing with the latest
version of your dependencies, as this is probably what most
of your users are going to get.
We can also add dev dependencies.
Dev dependencies can only be used
for testing and development.
When someone takes dependency in your package,
they won't be installing your dev dependency.
For example, we've added checks and petit parser
as dev dependencies to the constraints of the package.
If we look at the dependency graph of the scheduler app that
depends on constraint solver, the dependency graph
won't include the dev dependencies
of constraint solver.
But if we're working on constraint solver, where
it is the root project and resolving its dependencies,
we'll get a dependency graph that
includes the dev dependencies.
Using dev dependencies allows us to have dependencies
that are used for testing and development
but are not required by people using our package.
This is important because Dart only allows
one version of each package.
So if two packages need different versions
of the same package, like checks,
then they can't be used together unless their dependency
on checks is a dev dependency.
Now that we have some dependencies,
we can run Dart pub outdated and get a report
of what can be upgraded.
Here we see that the checks package is
upgradeable to a newer version.
If we run dart pub upgrade, we upgrade package checks
to a newer version.
This makes sense because 0.2.1 is
compatible with the constraint we have in the pupspec.yaml.
We don't get a newer version of the petite parser
because the constraint in pubspec.yaml
doesn't allow a newer major version.
The Dart pub upgrade command actually just ignores
the lock file and tries to get a newer version.
You can get the same result by deleting the lock file
and running dart pub get.
If we go back to the outdated report,
we also see that petit parser was not upgradable.
But a newer version of petit parser is resolvable.
This means that it's possible to get a newer
version of petit parser if we upgrade the constraints
in our pubspec.yaml.
Notice that sometimes it's not possible to get the latest
version of a dependency.
This is because we only allow one version of each package
in our dependency resolution.
This keeps the dependency tree lean
but can also cause version conflicts.
To upgrade a dependency that is held back by constraints
in pubspec.yaml we can use Dart pub upgrade--major-versions.
This will upgrade dependencies while ignoring the other bound
constraints and then make necessary changes
to pubspec.yaml.
So if we run this, we'll get a newer version of petite parser.
But we also see that this actually
modifies the constraints in our pubspec.yaml
because we've upgraded to a newer
major version of petite parser.
As previously mentioned, a new major version
may have breaking changes.
So it's a good idea to read the changelog of petite parser
when upgrading to a new major version.
It's often also necessary to update our code,
but not always.
The breaking change may not be affecting the parts of the API
you're using.
To sum up, when managing dependencies,
semantic versioning helps you quickly
assess the scope of a change, whether it's breaking or not.
Using version constraints, you can easily
upgrade to new compatible versions with Dart pub upgrade.
But you should always test your code
because newer versions might have new bugs.
For this, let's go back to John.
[MUSIC PLAYING]
JOHN RYAN: Now let's look at how you
can set up continuous integration for your package.
When you're working on open source code,
running tests for each change makes it easier
to accept contributions with confidence.
To make testing easy, the Dart team
maintains the setup Dart workflow
for GitHub actions, which can be enabled from your repository's
Actions tab.
The workflow sets up a Dart SDK for you
to use for your build steps.
The Dart SDK provides tools to fetch package dependencies,
check your code's formatting, run static analysis,
and run unit tests.
You can even configure the Dart analyzer to enforce specific
lint rules in your analysis_options.yaml file.
You can find a list of lint rules in the description.
The Dart team has also published a new package
for making assertions and unit tests called checks.
It preserves the static type information
of the object you're testing so that you
can get auto completion.
You can also collect code coverage
to track the percentage of your code that's covered by tests
and report this information to apps like coveralls.
This can help you identify when a new feature might
need more tests.
Now that our tests are running, let's
go back to Jonas to see if we're ready to publish.
[MUSIC PLAYING]
JONAS JENSEN: We're almost ready to publish,
but there's a few things we should check first.
If you don't have a license file,
the Publish command will ask you to create one.
Dart uses the BSD free license because it
allows others to use, modify, and distribute
the code with few limitations.
You can choose another license if you want to.
You can even write your own.
But it's a lot easier for others to use your package
if it's published under an existing open source license.
Publishing is forever.
Once a package version is published,
it can generally not be unpublished or changed.
You can publish newer versions of the same package,
but a published version can generally not be removed.
That's because the leading app package affects all the users
that depend on the package.
And we don't want app developers to be unable to update
or released their apps because some package that they
depend on has been unpublished.
There are, of course, exceptions for policy violations,
et cetera.
Read pub.dev/policy for more information.
While published package versions cannot be deleted,
it is possible to retract a published package version.
Retraction is not the same as deletion.
A retracted package version still exists.
In fact, users can continue to get the retracted package
version if they have the retracted package
version in their lock file, meaning
they've already started using it before it was retracted.
Then they can continue to do so.
But the version solver will not allow retracted version
when the user is adding a dependency
to their pubspec.yaml.
Technically, a retracted version can also
be forced by pinning it the version
in dependency overrides.
But in simple terms, you can say that retracting a package
version stops new users from adopting
the version that was retracted.
This can be useful if shortly after publishing a new version
you discover that it was broken in some way.
If we stop maintaining our package,
we can mark it as discontinued.
This won't prevent anyone from using it,
but it'll hide it from default search results on pub.dev,
and users will be warned when they're using it.
Now that we know that publishing is forever and hard to undo,
we might want to run a few checks before we publish.
We can do this by running Dart pub publish--dry-run.
In this case, it highlights that the changelog does not even
mention the version number we're trying to publish.
That's a strong indication that we might have forgotten
to write a changelog entry.
Once we resolve these warnings, we can publish by running Dart
pub publish without the -- dry run.
But before we hit y to confirm publishing,
it's always a good idea to review the list of files just
to check that you're not accidentally including
files you don't want to share.
Now let's check with John to see how this looks once published.
[MUSIC PLAYING]
JOHN RYAN: Let's look at our package home page.
This is the home page for a Dart package.
The readme is the first thing you see,
so it's a good idea to include a short description
at the top, followed by usage examples
and additional details that might be helpful for users
that are getting started.
The tabs at the top provide quick access
to the changelog, examples, and more.
The description for your package will
be displayed in the sidebar as well as the repository link.
You can also add topics to your package.
This is a new pub feature that makes
it easy to find other packages with the same topic.
This helps developers find similar packages
and discover your package.
Pub.dev also displays the license in the sidebar
and, if it's a known license, labels
it using the SPDX identifier.
Pub also generates API documentation
and creates a link in the sidebar.
When you add documentation comments to your package,
they'll be converted into an easy to read web
page hosted automatically using Dart's Dart.command.
You can use markdown syntax, including code blocks.
If you're an individual, small team, or a large organization,
you can publish multiple packages
using a publisher, which lets you create a group of package
maintainers and also helps your developers find
all of your published packages.
Your package will get an overall score
based on popularity, health, and maintenance after publishing.
Check the Scores tab to see if there are any issues that
need to be fixed.
The final result is a healthy, well-tested, stable package
that's ready for other developers to use.
We hope these tools help you ship great open source Dart
and Flutter packages using pub.
We can't wait to see what you build.
Thanks for watching.
[MUSIC PLAYING]