This page covers additional information that is specific to contributing to flutter/packages. If you aren't already familiar with the general guidance on Flutter contribution, start with Tree hygiene.
Most changes need version and CHANGELOG changes; see below for details.
In most cases, the easiest way to create them is to use the update-release-info
repository command. If you are adding a feature, use --version=minor
, otherwise --version=minimal
will almost always do the right thing.
Any change that needs to be published in order to take effect must update the version in pubspec.yaml
. There are very few exceptions:
PRs that only affect tests.
PRs that only affect unpublished parts of example apps.
PRs that only affect local development of the package (e.g., changes to ignored lints).
Breaking change batching (see below).
Non-code changes that we make to future-proof development in some way, but don't directly benefit clients. Examples of this include
stable
).(Unless you are a member of the Flutter team, you are likely not making changes that fall under this exemption.)
This is because the packages in flutter/packages use a continuous release model rather than a set release cadence. This model gets improvements to the community faster, makes regressions easier to pinpoint, and simplifies the release process.
(The override: no versioning needed
label can be added to skip this check if it fails, but only if the criteria above are met, or team members agree there is a compelling reason for a new exemption. Team members: please leave a comment when adding the override
label explaining the reason for the override.)
All version changes must have an accompanying CHANGELOG update. Even version-exempt changes should often update CHANGELOG by adding a special NEXT
entry at the top of CHANGELOG.md
(unless they only affect development of the package, such as test-only changes):
## NEXT * Description of the change. ## 1.0.2 ...
This policy exists because some changes (e.g., certain updates to examples) that do not need to be published may still be of interest to clients of a package.
The tooling errs on the side of false positives, so will sometimes flag changes that should be exempt for one of the reasons listed above. In these cases, or if the reviewer feels that the change would not be relevant to package clients, they can add the override: no changelog needed
label can to skip this check. Team members: please leave a comment when adding the override
label explaining the reason for the override.
For consistency, all CHANGELOG entries should follow a common style:
##
for the version line. A version line should have a blank line before and after it.*
for individual items.-
, use that instead for local consistency..
.**BREAKING CHANGE**:
, or **BREAKING CHANGES**:
if there is a sub-list of changes.Example:
## 2.0.0 * Adds the ability to fetch data from the future. * **BREAKING CHANGES**: * Removes the deprecated `neverCallThis` method. * URLs parameters are now `Uri`s rather than `String`s. ## 1.0.3 * Fixes a crash when the device teleports during a network operation.
NEXT
Note: If you are using update-release-info
, this will be handled correctly for you.
If you are adding a version change to a CHANGELOG that starts with NEXT
, and your change also doesn't require a version update, just add a description to the existing NEXT
list:
## NEXT * Description of your new change. * Existing entry. ## 1.0.2 ...
If your change does require a version change, do the same, but then replace NEXT
with the new version. For example:
## 1.0.3 * Description of your new change. * Existing entry. ## 1.0.2 ...
If you leave NEXT
when adding a version change, automated tests for your PR will fail.
Do I need to update the version if I'm just changing the README? Yes. Most people read the README on pub.dev, not GitHub, so a README change is not very useful unless it is published.
Do I need to update the version if I'm just changing comments? If the comment is intended for clients of the package (a ///
comment on anything exported by the package), then yes, since what developers using the package will see in their IDE will come from the published version. If the comment is only useful for someone working on the package (such as an implementation comment within a method, or a comment in a non-exported file), then no.
What do I do if I there are conflicts to those changes before or during review? This is common. You can leave the conflicts until you're at the end of the review process to avoid needing to resolve frequently. Including the version changes at the beginning despite the likelihood of conflicts makes it much harder to forget that step, and also means that a reviewer can easily fix it from the GitHub UI just before landing.
Because we prefer to minimize breaking changes to packages after 1.0, breaking changes do not always follow the normal one-version-change-per-PR approach. Instead, when making a breaking change consider whether there are other breaking changes that should be made at the same time, and discuss with other regular contributors. If there are multiple changes to make, they can be batched as follows:
publish_to: none
to the package, with a comment referencing the issue from step 1.publish_to: none
.We try to minimize external package dependencies as much as possible, where “external” means packages that are not generally within the control of the Flutter or Dart teams (Examples of non-external packages include sdk:
dependencies, packages in flutter/packages, and packages published by dart.dev), or by an organization with a track record of strong support for both engineering best practices and Flutter. This is for several reasons:
master
; if we can't guarantee that fixes for breaking changes can be landed immediately in dependencies, it can block our roller.If you are considering adding an external dependency:
dev_dependency
on an external package, pin it to a specific version if at all possible.dependency
on an external package in an example/
, pin it to a specific version if at all possible.The same general principles apply to native dependencies for plugins (e.g., dependencies specified in an Android Gradle file or iOS podspec file): minimize dependencies—and especially non-test dependencies—on libraries that are not created by either the platform vendor or another organization with a track record of strong support for engineering best practices in whom we can have a very high degree of confidence in ongoing support and prompt updates.
Some dependencies should only be linked as dev_dependencies, such as integration_test
. Known dev-only dependencies are checked by CI, and if you are adding a new dev-only dependency, consider adding it to the repository tooling checks.
The goal is to have any plugin feature work on every platform on which that feature makes sense; having a lot of features that are only partially implemented across platforms is often confusing and frustrating for developers trying to use those plugins. However, a single developer will not always have the expertise to implement a feature across all supported platforms.
Given that, we welcome PRs that only implement a feature for a subset of platforms, including just one. To set expectations for how such PRs will be handled:
They will not be fully reviewed until there's an understanding of what support would look like across other platforms, for several reasons:
The PR author isn't necessarily responsible for answering these questions. These cases will be noted in comments during PR triage; if the PR author, or others in the community, can contribute that information, that will certainly help. If not, that investigation will be part of the review process (in which case the review will likely take longer).
In some cases, a reviewer may wait on approving a PR for landing until there is a plan in place for landing implementations for other platforms. This is not a hard rule, and will be up to reviewer judgement. This could take a number of forms:
“Other platforms” might not always include all other platforms. E.g., a feature might be something that's much more likely to be useful on mobile than desktop, or the reverse, and so we might only wait for implementations of that subset. Again, this will be up to reviewer judgement.
In the case where a PR is put on hold for the reasons above, it should be clearly noted in the PR and on the associated issue. We encourage anyone interested in contributing more platform implementation PRs to comment in the bug in such cases.
In cases where a plugin offers API in the app-facing package that is not implemented on all platforms (or not implemented on some OS versions of some platforms), there should also be an API to query for support of that API at runtime. That is, plugin clients should always be able to write code like:
if (somePluginInstance.supportsDoingThing) { somePluginInstance.doThing(); }
rather than hard-coding a platform check. (See image_picker
's supportsImageSource
for an example.) This is for several reasons:
These APIs can take a variety of forms, including specific methods to check individual methods or parameters, a single method with an enum of API options, etc. Discuss with your reviewer what pattern is best suited to the specific case, and aim for consistency within a plugin when possible.
We currently have many cases of APIs that do not follow this guidance because they predate it. This is technical debt, rather than something that should be pointed to as a justification for adding new APIs that do not.
On some platforms, there are multiple native languages that can be used to write plugins; repository policy limits the languages that are used in some cases. These are currently the allowed languages for each platform:
hackers-ecosystem
channel on Discord.in_app_purchase
). This is the only case where Objective-C is allowed for macOS plugins.For all platforms, use of Dart for platform-specific features is both allowed and encouraged. While there are no specific rules about this in most plugins, in general we are moving toward having more logic written in Dart rather than a host language, as having more code in the project's primary language eases maintenance.
Most plugins in flutter/packages use pigeon
for communication between Dart and host-language code. Unless a package lists specific instructions in its CONTRIBUTING.md file, after changing a Pigeon interface definition file in the pigeons/
directory run:
$ dart run pigeon --input pigeons/[changed file]
If the package's tests have mocks, they likely include mocks of Pigeon-generated classes; see below for instructions to update the mocks to reflect the changes in the Pigeon-generated code.
Many packages use mockito
for unit tests. To regenerate mocks run:
$ dart run build_runner build --delete-conflicting-outputs
Most of the plugins in flutter/packages are federated. Because a logical plugin consists of multiple packages, and our CI tests using published package dependencies—in order to ensure that every PR can be published without breaking the ecosystem—changes that span multiple packages will need to be done in multiple PRs. This is common when adding new features.
We are investigating ways to streamline this, but currently the process for a multi-package PR is:
Create a PR that has all the changes, and update the pubspec.yaml files to have path-based dependency overrides. This can be done with the plugin repo tool's make-deps-path-based
command, targeting any dependency packages changed in the PR. For instance, for an Android-specific change to video_player
that required platform interface changes as well:
$ dart run script/tool/bin/flutter_plugin_tools.dart make-deps-path-based --target-dependencies=video_player_platform_interface,video_player_android
Upload that PR and get it into a state where the only test failure is the one complaining that you can't publish a package that has dependency overrides, then go through the normal process of getting reviews and approvals.
Once the combined PR is approved, create a new PR that has only the platform interface package changes from the PR above, and ask the reviewers of the main package to review that.
Once it has been reviewed, landed, and published, update the initial PR to:
master
.If there are any dependency overrides remaining, repeat the previous two steps with those packages. There should never be interdependencies between platform implementation packages, so all implementations should be able to be handled in a single new PR.
pubspec.yaml
. Even though it‘s possible to compile with only the interface package’s constraint updated, we don't want clients of the package who update without using pub upgrade
to have surprising failures (e.g., UnimplementedError
s) when using the new functionality due to not having the updated platform packages that implement the new feature.Once there are no dependency overrides, ask the reviewer to land the main PR.
Breaking changes to platform interfaces (any package ending in _platform_interface
) are strongly discouraged:
Because platform interfaces are not expected to be called by clients of the plugins, we favor backward compatibility over having a clean API at that layer.
In order to avoid accidental breaking changes that are missed in review, CI will by default fail for any breaking change to the platform interface. If you believe you need to make a breaking change, discuss with your reviewer and make sure they agree; once they do, add (or if you are not a project member, ask the reviewer to add) the override: allow breaking change
label, then re-run the failing check. You should also adding something like the following to the PR description:
## Breaking change justification <Insert good reason for breaking change here.>
This makes it easy for someone looking back at the change to find the reason for the breaking change without reading all of the comments.
Because platform implementations are subclasses of the platform interface and override its methods, almost any change to the parameters of a method is a breaking change. In particular, adding an optional parameter to a platform interface method is a breaking change even though it doesn't break callers of the method.
The least disruptive way to make a parameter change is:
Add a new method to the platform interface with the new parameters, whose default implementation calls the existing method. This is not a breaking change.
AuthenticationOptions
for authenticate
as an example), as this will allow adding other parameters in the future without breaking changes or new methods.@Deprecated
, and file an issue about updating all uses of that method in the other packages in the plugin.Note: The delegation of this method will feel backwards, because the implementing the new method in terms of the old method will discard the information from the new parameters. This is correct though, because the goal is to allow the app-facing package to call the new method regardless of whether implementation packages have implemented it. By forwarding to the existing method by default, implementation packages that haven‘t been updated will continue to have the behavior they had before (which, by necessity, won’t include support for the new parameters). Implementation packages that have been updated will override this implementation with their own that honors the new parameters.
Update the implementations to override both methods.
Update the app-facing package to call the new method.
At some later point the deprecated method can be cleaned up by:
Making a breaking change in the platform interface package to remove any deprecated methods.
Updating the app-facing package to allow either the new major version or the previous version of the platform interface (to minimize version lock issues with implementations).
Updating all the implementations to use the new major version, removing the override of the deprecated methods.
This cleanup step is low priority though; deprecated methods in a platform interface should be largely harmless, as it‘s an API with very few direct customers. It’s actually preferable to wait quite some time before doing this, as it gives any unendorsed third-party implementations that may exist more time to adapt to the change without disruption.
flutter/packages has a general policy of supporting the latest stable
version of Flutter, as well as the current master
. In practice, many packages often support older versions as well, as minimum version requirements are generally only updated when there is a specific need, such as using a new Flutter feature.
stable
. In this rare case, discuss with #hackers-ecosystem
as it requires adding conditional logic to the CI scripts.Most CI only runs against those two version. There are only minimal tests of versions older that current stable
(currently analysis only, for the previous two stable versions of Flutter).
master
and had to wait until a change reached stable
, that means you need to update the minimum version. (Adding a constraint update as soon as the PR fails on stable
tests is a good way to make sure you don't forget.)stable
, and it can‘t wait until that feature reaches stable
, ask in #hackers-ecosystem
to see if there’s a good solution.*_legacy
CI test, update the minimum version accordingly.The ecosystem team may also mass-update minimum versions from time to time, to reduce the potential of breaking untested versions (see below). In general, this should use a minimum version somewhat older than the current stable
, but in some special cases may use the current stable version instead.
Sometimes a change to a package accidentally breaks older version of Flutter; because the full CI test suite does not run anything older than the current stable, such breakage will not necessarily be caught by CI, and will only be discovered via issue reports. When that happens there are several options:
#hackers-ecosystem
to decide which makes sense for the specific case:## NEXT
PR for the package that updates its minimum version, to document the correct reality). This will require all affected users to find the issue to learn how to fix it, so should generally only be done if the number of people affected is likely to be small (e.g., when it only affects versions of Flutter that are several stable releases behind).All plugins in flutter/packages should follow the following conventions. Note that existing plugins do not currently always follow those conventions because they predate them. PRs that update plugins to follow conventions are welcome.
All plugins should be fully federated. This is to ensure that:
The only exception to this policy is plugins that are inherently specific to a single platform, such as espresso
.
All implementations should use in-package platform channels, for the reasons outlined in the proposal document. Most plugins that predate this policy and have a legacy “shared method channel” default implementation in the platform interface package, but it is not be used by any first-party implementations.
Having consistent error handling across platforms is an important part of providing a usable cross-platform API surface. Maintaining consistent errors directly from the native implementations is challenging since there is no easy way to share constants for error code strings across all the different languages, nor any clear reference point for what the possible errors are without reading all of the other implementations. It's also challenging to make changes, as it potentially requires changing every package in the federated plugin.
To ensure that the native errors are a coherent part of the interface, plugins that can throw PlatformException
s internally should follow these best practices:
Exception
subclass, including constants or enums for known error types (e.g., permission failures).export
that definition, and should include it in relevant API documentation.PlatformExceptions
that the native implementation is likely to throw, and convert them to the appropriate interface-defined error types.This means that in general, clients of a plugin should not be expected to see raw PlatformException
s created from error responses in native code. (This is not a strict rule; failure cases that are so obscure that clients would be unlikely to actually have specific handlers for them don't necessarily need to be converted to a common exception type.)
Note: Existing PlatformException
s are a de-facto part of the API, so updating plugins to follow this practice should be done as a breaking change.
The best practice for doing switch
es over enum
s varies depending on the situation:
enum
is defined in the same package as the switch
, the switch
should cover all cases with no fallback, as it can be updated at the same time as any changes to the enum.enum
is defined in a different package—this is not uncommon in federated plugins—the code should be robust against new enum values (unless it is just example code), since adding enum values is generally not considered a breaking change:default:
; instead put the fallback code outside the enum. This will cause the linter to flag code in other packages that we need to update when an enum
is changed. (This will often require the use of ignore: dead_code
.) The fallback is needed because even though we can and should update the package(s) containing the switch
as a follow-up, we cannot guarantee that someone won't use an older version of the package containing the switch
with a newer version of the package containing the enum
, so that combination must have some well-defined behavior.UnimplementedError
so the client knows they need a new version of the package.All new code samples in README.md
files must <?code-excerpt?>
pragmas to manage the code. With <?code-excerpt?>
, the source of truth for the code is an actual Dart file, which is analyzed, compiled, and tested by our CI. This ensures that it stays updated as the package APIs, Flutter, Dart, and repository analyzer settings change.
<?code-excerpt?>
-managed examplesIf a code block has a <?code-excerpt ...?>
tag just before it, it is already using code-excerpt
. To update it:
<?code-excerpt "lib/main.dart (AppLifecycle)"?>
comes from lib/main.dart
; if there is a <?code-excerpt path-base=""?>
directive earlier in the file, paths are resolved relative to the given path-base
instead of the markdown file's path).update-excerpts
repository tool command, which will update README.md
. From the root of the packages repository, the command to run this is: dart run script/tool/bin/flutter_plugin_tools.dart update-excerpts
To add a new code block, or fix a legacy code block:
Optionally, if this is the first code block to be added, add the following line to the start of the package's README.md
:
<?code-excerpt path-base="example/lib"?>
replacing example/lib
with the directory from which samples will be taken. This sets the base against which other paths are resolved. By default, the base is the path of the markdown file being updated. The base itself is resolved relative to the path of that file as well.
Find or write the code that you want to use in the example, then annotate it with #docregion
and #enddocregion
comments. Ideally the code should be in one of the examples, but you could also put the code in a test. The key is to put the code somewhere that is analyzed and, ideally, tested in CI.
For example:
// #docregion purple const Color purple = Color(0xFFE6E6FA); // #enddocregion purple
Reference the code from the README.md
(or other markdown file where you want the sample to appear):
<?code-excerpt "{example/lib/-relative filename} ({excerpt name})"?> ```dart ```
So if you added #docregion Foo
to example/lib/main.dart
and specified example/lib
as the path base, you would put the following in README.md
:
<?code-excerpt "main.dart (Foo)"?> ```dart ```
Follow the steps in the “Updating” section above to automatically fill the new block with the code excerpt.
Samples can come from any file. In XML and HTML files, use <!--#docregion sectionname-->
and <!--#enddocregion sectionname-->
. In CSS files, use /* #docregion sectionname */
and /* #enddocregion sectionname */
. In YAML files, use # #docregion sectionname
and # #enddocregion sectionname
. In C++, Dart, JS, Kotlin, Swift, and other similar languages, use // #docregion sectionname
and // #enddocregion sectionname
.
You can extract multiple segments into one sample by just having multiple docregions with the same name. The regions will be concatenated with a language-appropriate comment such as // ···
. You can control what this looks like by using the plaster
control, as in:
<?code-excerpt "main.dart (Foo)" plaster="more..."?> ```dart ```
To disable this feature, use plaster="none"
.
The plaster is indented as much as the #enddocregion
of the region where the splice occurs.
Regions are maximally unindented before being injected into the markdown files (while preserving relative indentation within the block).
Read the Swift migration design doc, which covers several important topics such as the “top down approach”. Feel free to leave any comments and feedback in the doc.
Read Google‘s Swift Style Guide and Apple’s API Design Guidelines (which is endorsed by Google's Swift Style Guide).
Pick a plugin that you are interested in from the umbrella GitHub issue. This is for multiple contributors to coordinate with each other, and to avoid duplicate or conflicting work.
Remove custom modulemap (example). This is due to a limitation in Cocoapods (details). This unfortunately means that we cannot use Test
submodules, and all ObjC headers are exposed publicly during the migration.
Add Swift dependency information to the podspec (example). Note that .h
and .m
should be removed from the spec.source_files
after migration is done.
After the plugin class is migrated to Swift, update pluginClass
in pubspec.yaml
(example).
Install swift-format. If you also contribute to the flutter engine, there is an issue with the swift-format under depot_tools
. Make sure to set up the export PATH properly to run your own copy of swift-format
.
Run ./script/tool_runner.sh format
To reduce the risk of regression, it is important to backfill unit tests to full coverage before the migration, and maintain full coverage during the migration. If possible (i.e., if adding testing does not require refactoring the plugin to make it testable), adding test coverage should be done in a separate PR before the conversion of the implementation.
We do not report code coverage on the CI, since the number can actually be misleading (for example, a 100% coverage may make PR reviewers think that everything is fine, which is not necessarily true). Instead, you (and your PR reviewer) should evaluate whether the test is complete, by actually reading the test code (rather than just looking at the coverage). You may well want to use a coverage tool as part of your evaluation.
During the migration, especially when backfilling tests, it is possible to discover existing bugs. Migration PRs should just focus on migration and should not contain bug fixes. You can either (1) fix the bug in ObjC first in a separate PR, or (2) if the bug is too hard to fix, just acknowledge the bug in a comment so that someone can fix it in the future.