From 22f956fae6c95e752f2508b1e41f0528d29efe17 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 7 Oct 2026 12:05:51 -0700 Subject: [PATCH 01/11] Adds styleguide change proposal for enum and package private --- ...0-style-guide-package-private-and-enums.md | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 rfc/710.0000-style-guide-package-private-and-enums.md diff --git a/rfc/710.0000-style-guide-package-private-and-enums.md b/rfc/710.0000-style-guide-package-private-and-enums.md new file mode 100644 index 0000000..a9b40ae --- /dev/null +++ b/rfc/710.0000-style-guide-package-private-and-enums.md @@ -0,0 +1,101 @@ +--- +type: rfc +rfc: '710.0000' +title: Style guide changes for package private and enums +description: Updates the Flutter style guide and breaking change policy for package-private APIs, @internal, and enums. +status: draft +created: 2026-10-06T00:00:00Z +updated: 2026-10-07T19:01:48.841607Z +tags: + - 710-style-guide + - 030-breaking-changes +authors: + - https://github.com/chunhtai +--- + +# RFC 710.0000: Style guide changes for package private and enums + +## Overview + +This document proposes updating the [Flutter repository style guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md) and [Flutter breaking change policy](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes) to adopt `@internal` for package-private APIs and establish conventions for public enums. + +Declarations marked `@internal` are outside the public API contract; modifying or removing them is not a breaking change. For public APIs, a standard `enum` is used when consumers must handle all values exhaustively (making new values a breaking change), and a class with `static const` fields is used when consumers need a fallback (making new values non-breaking). + +## Context + +### Package-private APIs + +The style guide discourages package-private APIs beyond library-private (`_`) declarations and requires every file in `lib/src//` to be exported by `lib/.dart`. In practice, when file-scoped `_` privacy is too narrow, the framework already omits `_`-prefixed files in `lib/src/` from barrel exports (such as `lib/src/widgets/_window.dart` and `lib/src/foundation/_features.dart`). + +Relying on filename conventions alone risks accidental leaks. Because `lib/src/` files are re-exported by default, moving code or sharing a non-private class within a layer can inadvertently expose it to external consumers, making future edits breaking changes. Marking these declarations `@internal` triggers an `invalid_internal_annotation` analyzer warning if exported, prompting contributors to move them to an unexported `_` file or hide them in the barrel export. + +### Enums + +Inside the Flutter codebase, requiring exhaustive `switch` statements and expressions over `enum` values (without `default` branches) is intentional and remains unchanged. However, the style guide lacks guidelines for designing enum-like types in public APIs, and the breaking change policy does not explicitly classify adding a value to a public `enum` as a breaking change. + +Because external callers also switch exhaustively over public `enum` types, adding a new value breaks downstream code. Without criteria for choosing between a standard `enum` and a class with `static const` fields, public APIs in the framework are inconsistent: + +* `TargetPlatform` and `AppLifecycleState` are `enum` declarations even though consumers typically handle only specific values with a fallback. Adding new values breaks callers. +* `TextInputType` and `LogicalKeyboardKey` use classes with `static const` fields to allow non-breaking additions, but the style guide does not document when to use this pattern instead of an `enum`. + +## Proposal + +### Breaking change policy exemption + +Modifying, renaming, or removing any declaration annotated with `@internal` is not a breaking change and requires no deprecation period or announcement. + +### Package-private APIs (`@internal`) + +Use `@internal` from `package:meta` when an implementation detail is shared across files within the same layer (`lib/src//`) or accessed by framework unit tests in `packages/flutter/test/`. + +#### Placement rules + +* `@internal` may be applied to top-level declarations and to `static` members or constructors of public classes inside `lib/src/`. +* In any file in `lib/src/` not exported by a barrel file, all public top-level declarations must be annotated with `@internal`. +* `@internal` must not be applied to an instance member of a public class unless both: + * The enclosing class has the `base`, `final`, or `sealed` modifier so external packages cannot `implement` it. + * The instance member is annotated with `@nonVirtual` so subclasses cannot override it. + +#### Barrel exports and file naming + +* Any file in `lib/src//` not exported by `lib/.dart` must have a `_` filename prefix (such as `lib/src/widgets/_window.dart`), and have all public top-level declarations annotated with `@internal`. +* When an `@internal` top-level declaration shares a file with public declarations, `lib/.dart` and any intra-`src/` re-export must exclude it with `hide` (such as `export 'src/widgets/foo.dart' hide InternalBar;`). +* Cross-layer imports in `lib/src/` must continue importing top-level barrel files (`package:flutter/.dart`) rather than another layer's `lib/src/` files. + +### Enums + +#### Non-public enums + +Library-private (`_`) and `@internal` enums must use a standard `enum` declaration so framework `switch` statements and expressions are exhaustively checked. + +#### Public APIs + +Choose between a standard `enum` and an enum-like class based on whether consumers must handle every value exhaustively, rather than whether new values might be added later: + +##### Standard `enum` (exhaustive) + +Use a standard `enum` when consumers are expected to handle every case without a `default` or `_` branch (such as `Axis`, `AxisDirection`, `VerticalDirection`, and `GrowthDirection`). + +* Provides compile-time exhaustiveness checking. +* Adding a value to a public `enum` is a breaking change, forcing consumers to handle the new case. + +##### Enum-like class with `static const` values (non-exhaustive) + +Use a `final` class with a private `const` constructor and `static const` fields when consumers handle a subset of values with a `default` or `_` fallback (such as `TextInputType` and `LogicalKeyboardKey`; existing enums like `TargetPlatform` and `AppLifecycleState` also fit this category). + +* Adding a `static const` field is not a breaking change. +* Does not provide compile-time exhaustiveness checking. + +## Open questions + +### Removing discouragement of `@visibleForTesting` + +The style guide section "Avoid using `@visibleForTesting`" advises against `@visibleForTesting` on public declarations, preferring APIs testable through their public interfaces, though contributors still use it when necessary. Should the style guide remove its discouragement of `@visibleForTesting` or keep the current stance? + +### Adopting `@experimental` for cutting-edge development + +New public APIs immediately fall under the breaking change policy, requiring an RFC, deprecation cycle, and migration guide to modify or remove. Should Flutter adopt [`@experimental`](https://pub.dev/documentation/meta/latest/meta/experimental-constant.html) from `package:meta` to exempt cutting-edge APIs from the breaking change policy? + +### Handling feature flag files and the cross-layer import rule + +`lib/src/foundation/_features.dart` defines `@internal` feature flags consumed by higher layers (for example, `lib/src/widgets/binding.dart` imports `../foundation/_features.dart`). Because `@internal` symbols cannot be exported by `lib/foundation.dart`. What should be done about feature flags? From 37fccd016a97a94062e8fb687d90fc2014d28645 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 7 Oct 2026 12:36:11 -0700 Subject: [PATCH 02/11] update --- rfc/710.0000-style-guide-package-private-and-enums.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/710.0000-style-guide-package-private-and-enums.md b/rfc/710.0000-style-guide-package-private-and-enums.md index a9b40ae..c56ca3d 100644 --- a/rfc/710.0000-style-guide-package-private-and-enums.md +++ b/rfc/710.0000-style-guide-package-private-and-enums.md @@ -62,7 +62,7 @@ Use `@internal` from `package:meta` when an implementation detail is shared acro * When an `@internal` top-level declaration shares a file with public declarations, `lib/.dart` and any intra-`src/` re-export must exclude it with `hide` (such as `export 'src/widgets/foo.dart' hide InternalBar;`). * Cross-layer imports in `lib/src/` must continue importing top-level barrel files (`package:flutter/.dart`) rather than another layer's `lib/src/` files. -### Enums +### Enum conventions #### Non-public enums From 1f0f3f542fdaa004eba978f61e30a72c924d3e8f Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 7 Oct 2026 12:41:10 -0700 Subject: [PATCH 03/11] update --- rfc/710.0000-style-guide-package-private-and-enums.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rfc/710.0000-style-guide-package-private-and-enums.md b/rfc/710.0000-style-guide-package-private-and-enums.md index c56ca3d..b457599 100644 --- a/rfc/710.0000-style-guide-package-private-and-enums.md +++ b/rfc/710.0000-style-guide-package-private-and-enums.md @@ -19,7 +19,9 @@ authors: This document proposes updating the [Flutter repository style guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md) and [Flutter breaking change policy](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes) to adopt `@internal` for package-private APIs and establish conventions for public enums. -Declarations marked `@internal` are outside the public API contract; modifying or removing them is not a breaking change. For public APIs, a standard `enum` is used when consumers must handle all values exhaustively (making new values a breaking change), and a class with `static const` fields is used when consumers need a fallback (making new values non-breaking). +* Declarations marked `@internal` are outside the public API contract; modifying or removing them is not a breaking change. + +* For public APIs, a standard `enum` is used when consumers must handle all values exhaustively (making new values a breaking change), and a class with `static const` fields is used when consumers need a fallback (making new values non-breaking). ## Context @@ -77,7 +79,7 @@ Choose between a standard `enum` and an enum-like class based on whether consume Use a standard `enum` when consumers are expected to handle every case without a `default` or `_` branch (such as `Axis`, `AxisDirection`, `VerticalDirection`, and `GrowthDirection`). * Provides compile-time exhaustiveness checking. -* Adding a value to a public `enum` is a breaking change, forcing consumers to handle the new case. +* Adding a value to a public `enum` is a breaking change and requires to go through the breaking change process. ##### Enum-like class with `static const` values (non-exhaustive) From bb70f0c9e9125b3683bd1c44d4cd1c19d83bd026 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Thu, 8 Oct 2026 11:40:58 -0700 Subject: [PATCH 04/11] move enum out --- ...> 710.0000-style-guide-package-private.md} | 49 +++---------------- 1 file changed, 6 insertions(+), 43 deletions(-) rename rfc/{710.0000-style-guide-package-private-and-enums.md => 710.0000-style-guide-package-private.md} (58%) diff --git a/rfc/710.0000-style-guide-package-private-and-enums.md b/rfc/710.0000-style-guide-package-private.md similarity index 58% rename from rfc/710.0000-style-guide-package-private-and-enums.md rename to rfc/710.0000-style-guide-package-private.md index b457599..999e2b2 100644 --- a/rfc/710.0000-style-guide-package-private-and-enums.md +++ b/rfc/710.0000-style-guide-package-private.md @@ -1,11 +1,11 @@ --- type: rfc rfc: '710.0000' -title: Style guide changes for package private and enums -description: Updates the Flutter style guide and breaking change policy for package-private APIs, @internal, and enums. +title: Style guide changes for package private +description: Updates the Flutter style guide and breaking change policy for package-private APIs and @internal. status: draft created: 2026-10-06T00:00:00Z -updated: 2026-10-07T19:01:48.841607Z +updated: 2026-10-08T16:48:00Z tags: - 710-style-guide - 030-breaking-changes @@ -13,33 +13,20 @@ authors: - https://github.com/chunhtai --- -# RFC 710.0000: Style guide changes for package private and enums +# RFC 710.0000: Style guide changes for package private ## Overview -This document proposes updating the [Flutter repository style guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md) and [Flutter breaking change policy](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes) to adopt `@internal` for package-private APIs and establish conventions for public enums. +This document proposes updating the [Flutter repository style guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md) and [Flutter breaking change policy](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes) to adopt `@internal` for package-private APIs. -* Declarations marked `@internal` are outside the public API contract; modifying or removing them is not a breaking change. - -* For public APIs, a standard `enum` is used when consumers must handle all values exhaustively (making new values a breaking change), and a class with `static const` fields is used when consumers need a fallback (making new values non-breaking). +Declarations marked `@internal` are outside the public API contract; modifying or removing them is not a breaking change. ## Context -### Package-private APIs - The style guide discourages package-private APIs beyond library-private (`_`) declarations and requires every file in `lib/src//` to be exported by `lib/.dart`. In practice, when file-scoped `_` privacy is too narrow, the framework already omits `_`-prefixed files in `lib/src/` from barrel exports (such as `lib/src/widgets/_window.dart` and `lib/src/foundation/_features.dart`). Relying on filename conventions alone risks accidental leaks. Because `lib/src/` files are re-exported by default, moving code or sharing a non-private class within a layer can inadvertently expose it to external consumers, making future edits breaking changes. Marking these declarations `@internal` triggers an `invalid_internal_annotation` analyzer warning if exported, prompting contributors to move them to an unexported `_` file or hide them in the barrel export. -### Enums - -Inside the Flutter codebase, requiring exhaustive `switch` statements and expressions over `enum` values (without `default` branches) is intentional and remains unchanged. However, the style guide lacks guidelines for designing enum-like types in public APIs, and the breaking change policy does not explicitly classify adding a value to a public `enum` as a breaking change. - -Because external callers also switch exhaustively over public `enum` types, adding a new value breaks downstream code. Without criteria for choosing between a standard `enum` and a class with `static const` fields, public APIs in the framework are inconsistent: - -* `TargetPlatform` and `AppLifecycleState` are `enum` declarations even though consumers typically handle only specific values with a fallback. Adding new values breaks callers. -* `TextInputType` and `LogicalKeyboardKey` use classes with `static const` fields to allow non-breaking additions, but the style guide does not document when to use this pattern instead of an `enum`. - ## Proposal ### Breaking change policy exemption @@ -64,30 +51,6 @@ Use `@internal` from `package:meta` when an implementation detail is shared acro * When an `@internal` top-level declaration shares a file with public declarations, `lib/.dart` and any intra-`src/` re-export must exclude it with `hide` (such as `export 'src/widgets/foo.dart' hide InternalBar;`). * Cross-layer imports in `lib/src/` must continue importing top-level barrel files (`package:flutter/.dart`) rather than another layer's `lib/src/` files. -### Enum conventions - -#### Non-public enums - -Library-private (`_`) and `@internal` enums must use a standard `enum` declaration so framework `switch` statements and expressions are exhaustively checked. - -#### Public APIs - -Choose between a standard `enum` and an enum-like class based on whether consumers must handle every value exhaustively, rather than whether new values might be added later: - -##### Standard `enum` (exhaustive) - -Use a standard `enum` when consumers are expected to handle every case without a `default` or `_` branch (such as `Axis`, `AxisDirection`, `VerticalDirection`, and `GrowthDirection`). - -* Provides compile-time exhaustiveness checking. -* Adding a value to a public `enum` is a breaking change and requires to go through the breaking change process. - -##### Enum-like class with `static const` values (non-exhaustive) - -Use a `final` class with a private `const` constructor and `static const` fields when consumers handle a subset of values with a `default` or `_` fallback (such as `TextInputType` and `LogicalKeyboardKey`; existing enums like `TargetPlatform` and `AppLifecycleState` also fit this category). - -* Adding a `static const` field is not a breaking change. -* Does not provide compile-time exhaustiveness checking. - ## Open questions ### Removing discouragement of `@visibleForTesting` From 17077d671528f4852fa3d71cd419082c5f3c482f Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Fri, 9 Oct 2026 15:44:04 -0700 Subject: [PATCH 05/11] update --- rfc/710.0000-style-guide-package-private.md | 51 +++++++++++---------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/rfc/710.0000-style-guide-package-private.md b/rfc/710.0000-style-guide-package-private.md index 999e2b2..a86f275 100644 --- a/rfc/710.0000-style-guide-package-private.md +++ b/rfc/710.0000-style-guide-package-private.md @@ -5,7 +5,7 @@ title: Style guide changes for package private description: Updates the Flutter style guide and breaking change policy for package-private APIs and @internal. status: draft created: 2026-10-06T00:00:00Z -updated: 2026-10-08T16:48:00Z +updated: 2026-10-09T22:16:00Z tags: - 710-style-guide - 030-breaking-changes @@ -17,50 +17,53 @@ authors: ## Overview -This document proposes updating the [Flutter repository style guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md) and [Flutter breaking change policy](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes) to adopt `@internal` for package-private APIs. +This document proposes updating the [Flutter repository style guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md) and [Flutter breaking change policy](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes) to adopt `@internal` for package-private APIs across `flutter/flutter` and `flutter/packages`. -Declarations marked `@internal` are outside the public API contract; modifying or removing them is not a breaking change. +Declarations in `lib/src/` that are not exported by public barrel files (`lib/.dart`) and are marked `@internal` fall outside the public API contract; modifying or removing them is not a breaking change. ## Context -The style guide discourages package-private APIs beyond library-private (`_`) declarations and requires every file in `lib/src//` to be exported by `lib/.dart`. In practice, when file-scoped `_` privacy is too narrow, the framework already omits `_`-prefixed files in `lib/src/` from barrel exports (such as `lib/src/widgets/_window.dart` and `lib/src/foundation/_features.dart`). +The style guide discourages package-private APIs beyond library-private (`_`) declarations and requires every file in `lib/src//` to be exported by `lib/.dart`. When an implementation detail is needed across multiple files within a layer, contributors are left with two poor options: -Relying on filename conventions alone risks accidental leaks. Because `lib/src/` files are re-exported by default, moving code or sharing a non-private class within a layer can inadvertently expose it to external consumers, making future edits breaking changes. Marking these declarations `@internal` triggers an `invalid_internal_annotation` analyzer warning if exported, prompting contributors to move them to an unexported `_` file or hide them in the barrel export. +* Keep declarations library-private (`_`), which forces all related code into a single large file (or requires `part`/`part of`, which exposes every `_` declaration across all part files and removes file-level encapsulation). +* Expose implementation details as public APIs (such as `FocusAttachment` or `FocusTraversalPolicy.invalidateScopeData`), which commits the framework to backward compatibility and discourages refactoring. + +In practice, the framework already omits `_`-prefixed files in `lib/src/` from barrel exports (such as `lib/src/widgets/_accessibility_evaluations.dart`, `lib/src/widgets/_window.dart`, and `lib/src/foundation/_features.dart`), and `flutter/packages` already uses `@internal` with `hide` (such as `GoRouterStateRegistry` in `go_router`). + +Relying on filename conventions alone risks accidental leaks. Because `lib/src/` files are re-exported by default, moving code or sharing a non-private class within a layer can inadvertently expose it to external consumers, making future edits breaking changes. Marking these declarations `@internal` triggers an [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element) analyzer error if exported from a public barrel file, and triggers [`invalid_use_of_internal_member`](https://dart.dev/tools/diagnostics/invalid_use_of_internal_member) if external consumers import `lib/src/` directly. ## Proposal -### Breaking change policy exemption +### Breaking change policy and customer tests -Modifying, renaming, or removing any declaration annotated with `@internal` is not a breaking change and requires no deprecation period or announcement. +* Modifying, renaming, or removing any declaration in `lib/src/` that is not exported by a public barrel file and is annotated with `@internal` is not a breaking change and requires no deprecation period or announcement. +* Registered tests in [`flutter/tests`](https://github.com/flutter/tests) must not import `package:flutter/src/` or depend on `@internal` APIs. Breakages in customer tests caused by `package:flutter/src/` imports or `@internal` usage are not breaking changes, and contributors may disable or update offending customer tests when modifying `@internal` declarations. ### Package-private APIs (`@internal`) Use `@internal` from `package:meta` when an implementation detail is shared across files within the same layer (`lib/src//`) or accessed by framework unit tests in `packages/flutter/test/`. +#### Relationship to `@visibleForTesting` and feature flags + +* `@internal` is for package-private declarations omitted or hidden from public barrel exports. +* `@visibleForTesting` remains discouraged on public declarations (except when combined with `@protected`), because declarations exported by a public barrel file remain reachable by external consumers without importing `lib/src/`. + #### Placement rules * `@internal` may be applied to top-level declarations and to `static` members or constructors of public classes inside `lib/src/`. -* In any file in `lib/src/` not exported by a barrel file, all public top-level declarations must be annotated with `@internal`. +* In any file in `lib/src/` not exported by a barrel file, all public top-level declarations must be annotated with `@internal` (excluding conditional-import implementation files such as `_*_io.dart` and `_*_web.dart`). * `@internal` must not be applied to an instance member of a public class unless both: - * The enclosing class has the `base`, `final`, or `sealed` modifier so external packages cannot `implement` it. + * The enclosing class has the `base`, `final`, or `sealed` modifier so external packages cannot `implement` it (preferring `final` or `sealed`, since adding a new member to a `base` class can still collide with a member in an external subclass). * The instance member is annotated with `@nonVirtual` so subclasses cannot override it. #### Barrel exports and file naming -* Any file in `lib/src//` not exported by `lib/.dart` must have a `_` filename prefix (such as `lib/src/widgets/_window.dart`), and have all public top-level declarations annotated with `@internal`. -* When an `@internal` top-level declaration shares a file with public declarations, `lib/.dart` and any intra-`src/` re-export must exclude it with `hide` (such as `export 'src/widgets/foo.dart' hide InternalBar;`). -* Cross-layer imports in `lib/src/` must continue importing top-level barrel files (`package:flutter/.dart`) rather than another layer's `lib/src/` files. - -## Open questions - -### Removing discouragement of `@visibleForTesting` - -The style guide section "Avoid using `@visibleForTesting`" advises against `@visibleForTesting` on public declarations, preferring APIs testable through their public interfaces, though contributors still use it when necessary. Should the style guide remove its discouragement of `@visibleForTesting` or keep the current stance? - -### Adopting `@experimental` for cutting-edge development - -New public APIs immediately fall under the breaking change policy, requiring an RFC, deprecation cycle, and migration guide to modify or remove. Should Flutter adopt [`@experimental`](https://pub.dev/documentation/meta/latest/meta/experimental-constant.html) from `package:meta` to exempt cutting-edge APIs from the breaking change policy? +* Drop the requirement that every file in `lib/src//` be exported by `lib/.dart` (superseding [flutter/flutter#108632](https://github.com/flutter/flutter/issues/108632)). +* Any file in `lib/src//` not exported by `lib/.dart` must have a `_` filename prefix (such as `lib/src/widgets/_accessibility_evaluations.dart`), and have all public top-level declarations annotated with `@internal`. +* When an `@internal` top-level declaration shares a file with public declarations, `lib/.dart` must exclude it with `hide` (such as `export 'src/widgets/foo.dart' hide InternalBar;`). Omitting `hide` in `lib/.dart` triggers [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element). (`hide` is not required on intra-`src/` re-exports.) +* Cross-layer imports in `lib/src/` must continue importing top-level barrel files (`package:flutter/.dart`) rather than another layer's `lib/src/` files, with `lib/src/foundation/_features.dart` as the only permitted exception for framework-wide feature flags. -### Handling feature flag files and the cross-layer import rule +#### Enforcement -`lib/src/foundation/_features.dart` defines `@internal` feature flags consumed by higher layers (for example, `lib/src/widgets/binding.dart` imports `../foundation/_features.dart`). Because `@internal` symbols cannot be exported by `lib/foundation.dart`. What should be done about feature flags? +* Accidental public exports of `@internal` declarations are caught by the analyzer's [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element) diagnostic, and external usage is caught by [`invalid_use_of_internal_member`](https://dart.dev/tools/diagnostics/invalid_use_of_internal_member) and `implementation_imports`. +* Repository-specific rules (requiring unexported `lib/src/` files to use a `_` prefix and annotate public top-level declarations with `@internal`, and enforcing instance-member placement rules) will be enforced via custom analyzer lint rules. From f2eacc70833c1ab03e4dc790395e4abdbd81a084 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 19 Oct 2026 14:55:36 -0700 Subject: [PATCH 06/11] update --- rfc/710.0000-style-guide-package-private.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/rfc/710.0000-style-guide-package-private.md b/rfc/710.0000-style-guide-package-private.md index a86f275..1af5324 100644 --- a/rfc/710.0000-style-guide-package-private.md +++ b/rfc/710.0000-style-guide-package-private.md @@ -26,7 +26,7 @@ Declarations in `lib/src/` that are not exported by public barrel files (`lib//` to be exported by `lib/.dart`. When an implementation detail is needed across multiple files within a layer, contributors are left with two poor options: * Keep declarations library-private (`_`), which forces all related code into a single large file (or requires `part`/`part of`, which exposes every `_` declaration across all part files and removes file-level encapsulation). -* Expose implementation details as public APIs (such as `FocusAttachment` or `FocusTraversalPolicy.invalidateScopeData`), which commits the framework to backward compatibility and discourages refactoring. +* Expose implementation details as public APIs (such as `NavigatorState.finalizeRoute` shared between `navigator.dart` and `routes.dart`, or `HardwareKeyboard.syncKeyboardState` shared between `hardware_keyboard.dart` and `binding.dart` in `services`), which commits the framework to backward compatibility and discourages refactoring. In practice, the framework already omits `_`-prefixed files in `lib/src/` from barrel exports (such as `lib/src/widgets/_accessibility_evaluations.dart`, `lib/src/widgets/_window.dart`, and `lib/src/foundation/_features.dart`), and `flutter/packages` already uses `@internal` with `hide` (such as `GoRouterStateRegistry` in `go_router`). @@ -43,18 +43,17 @@ Relying on filename conventions alone risks accidental leaks. Because `lib/src/` Use `@internal` from `package:meta` when an implementation detail is shared across files within the same layer (`lib/src//`) or accessed by framework unit tests in `packages/flutter/test/`. -#### Relationship to `@visibleForTesting` and feature flags +#### Relationship to `@visibleForTesting` and `@Deprecated` * `@internal` is for package-private declarations omitted or hidden from public barrel exports. +* `@internal` must not be used on APIs meant to be removed; use `@Deprecated` instead. * `@visibleForTesting` remains discouraged on public declarations (except when combined with `@protected`), because declarations exported by a public barrel file remain reachable by external consumers without importing `lib/src/`. #### Placement rules -* `@internal` may be applied to top-level declarations and to `static` members or constructors of public classes inside `lib/src/`. +* `@internal` may only be applied to top-level declarations (classes, mixins, enums, extensions, typedefs, functions, and variables) inside `lib/src/`. +* Do not apply `@internal` directly to members or constructors of a publicly exported class, because `export ... hide` cannot hide class members. Instead, keep the implementation library-private (`_`) and expose it within the layer via an `@internal` `extension` declared in the same file and excluded from `lib/.dart` with `hide` (an explicit exception to the style guide rule against `extension`s). * In any file in `lib/src/` not exported by a barrel file, all public top-level declarations must be annotated with `@internal` (excluding conditional-import implementation files such as `_*_io.dart` and `_*_web.dart`). -* `@internal` must not be applied to an instance member of a public class unless both: - * The enclosing class has the `base`, `final`, or `sealed` modifier so external packages cannot `implement` it (preferring `final` or `sealed`, since adding a new member to a `base` class can still collide with a member in an external subclass). - * The instance member is annotated with `@nonVirtual` so subclasses cannot override it. #### Barrel exports and file naming @@ -66,4 +65,4 @@ Use `@internal` from `package:meta` when an implementation detail is shared acro #### Enforcement * Accidental public exports of `@internal` declarations are caught by the analyzer's [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element) diagnostic, and external usage is caught by [`invalid_use_of_internal_member`](https://dart.dev/tools/diagnostics/invalid_use_of_internal_member) and `implementation_imports`. -* Repository-specific rules (requiring unexported `lib/src/` files to use a `_` prefix and annotate public top-level declarations with `@internal`, and enforcing instance-member placement rules) will be enforced via custom analyzer lint rules. +* Repository-specific rules (requiring unexported `lib/src/` files to use a `_` prefix and annotate public top-level declarations with `@internal`, and disallowing `@internal` on members of publicly exported classes) will be enforced via custom analyzer lint rules. From cb557b6cd3ff30420823d3103f7f742d819aff8b Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 19 Oct 2026 16:25:11 -0700 Subject: [PATCH 07/11] update --- rfc/710.0000-style-guide-package-private.md | 141 ++++++++++++++++++-- 1 file changed, 132 insertions(+), 9 deletions(-) diff --git a/rfc/710.0000-style-guide-package-private.md b/rfc/710.0000-style-guide-package-private.md index 1af5324..45cda82 100644 --- a/rfc/710.0000-style-guide-package-private.md +++ b/rfc/710.0000-style-guide-package-private.md @@ -5,7 +5,7 @@ title: Style guide changes for package private description: Updates the Flutter style guide and breaking change policy for package-private APIs and @internal. status: draft created: 2026-10-06T00:00:00Z -updated: 2026-10-09T22:16:00Z +updated: 2026-10-19T22:22:00Z tags: - 710-style-guide - 030-breaking-changes @@ -17,15 +17,21 @@ authors: ## Overview -This document proposes updating the [Flutter repository style guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md) and [Flutter breaking change policy](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes) to adopt `@internal` for package-private APIs across `flutter/flutter` and `flutter/packages`. +This document proposes updating the [Flutter repository style guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md) and [Flutter breaking change policy](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes) to discourage rather than ban package-private APIs across `flutter/flutter` and `flutter/packages`, and to adopt `@internal` when package-private APIs are needed. Declarations in `lib/src/` that are not exported by public barrel files (`lib/.dart`) and are marked `@internal` fall outside the public API contract; modifying or removing them is not a breaking change. +Package-private APIs are permitted only for: + +* Web and I/O conditional imports. +* Experimental APIs that are annotated with `@internal` and gated behind a feature flag. +* Internal-facing APIs shared across files within the same layer or accessed by unit tests, when library-private declarations are impractical. + ## Context -The style guide discourages package-private APIs beyond library-private (`_`) declarations and requires every file in `lib/src//` to be exported by `lib/.dart`. When an implementation detail is needed across multiple files within a layer, contributors are left with two poor options: +The style guide bans package-private APIs beyond library-private (`_`) declarations and requires every file in `lib/src//` to be exported by `lib/.dart`. When an implementation detail is needed across multiple files within a layer, contributors are left with two poor options: -* Keep declarations library-private (`_`), which forces all related code into a single large file (or requires `part`/`part of`, which exposes every `_` declaration across all part files and removes file-level encapsulation). +* Keep declarations library-private (`_`), which forces all related code into a single large file. * Expose implementation details as public APIs (such as `NavigatorState.finalizeRoute` shared between `navigator.dart` and `routes.dart`, or `HardwareKeyboard.syncKeyboardState` shared between `hardware_keyboard.dart` and `binding.dart` in `services`), which commits the framework to backward compatibility and discourages refactoring. In practice, the framework already omits `_`-prefixed files in `lib/src/` from barrel exports (such as `lib/src/widgets/_accessibility_evaluations.dart`, `lib/src/widgets/_window.dart`, and `lib/src/foundation/_features.dart`), and `flutter/packages` already uses `@internal` with `hide` (such as `GoRouterStateRegistry` in `go_router`). @@ -41,28 +47,145 @@ Relying on filename conventions alone risks accidental leaks. Because `lib/src/` ### Package-private APIs (`@internal`) -Use `@internal` from `package:meta` when an implementation detail is shared across files within the same layer (`lib/src//`) or accessed by framework unit tests in `packages/flutter/test/`. +Package-private APIs remain discouraged in general, rather than banned. Unexported files and package-private declarations in `lib/src/` are permitted only in the following cases: + +* Experimental APIs that are not yet ready for public stability guarantees. These APIs must be both annotated with `@internal` and gated behind a feature flag. +* Internal-facing APIs shared across files within the same layer (`lib/src//`) or accessed by unit tests. Contributors should use true library-private (`_`) declarations first, and only use `@internal` package-private APIs when the file size gets out of hand or there is a good reason to separate code into multiple files for readability. #### Relationship to `@visibleForTesting` and `@Deprecated` * `@internal` is for package-private declarations omitted or hidden from public barrel exports. * `@internal` must not be used on APIs meant to be removed; use `@Deprecated` instead. -* `@visibleForTesting` remains discouraged on public declarations (except when combined with `@protected`), because declarations exported by a public barrel file remain reachable by external consumers without importing `lib/src/`. +* `@visibleForTesting` remains discouraged on public declarations, because declarations exported by a public barrel file remain reachable by external consumers without importing `lib/src/`. #### Placement rules * `@internal` may only be applied to top-level declarations (classes, mixins, enums, extensions, typedefs, functions, and variables) inside `lib/src/`. * Do not apply `@internal` directly to members or constructors of a publicly exported class, because `export ... hide` cannot hide class members. Instead, keep the implementation library-private (`_`) and expose it within the layer via an `@internal` `extension` declared in the same file and excluded from `lib/.dart` with `hide` (an explicit exception to the style guide rule against `extension`s). -* In any file in `lib/src/` not exported by a barrel file, all public top-level declarations must be annotated with `@internal` (excluding conditional-import implementation files such as `_*_io.dart` and `_*_web.dart`). + +For example, `lib/src/widgets/route_state.dart` can keep the implementation private while allowing another file in the layer to invoke it through an internal extension: + +```dart +import 'package:meta/meta.dart'; + +class RouteState { + void _finalizeRoute() { + // Implementation. + } +} + +@internal +extension RouteStateInternal on RouteState { + void finalizeRoute() => _finalizeRoute(); +} +``` + +The public barrel excludes the extension: + +```dart +export 'src/widgets/route_state.dart' hide RouteStateInternal; +``` + +An internal file in the same layer can still import the source file and use the extension: + +```dart +import 'route_state.dart'; + +void finish(RouteState state) { + state.finalizeRoute(); +} +``` + +* In any file in `lib/src/` not exported by a barrel file, all public top-level declarations must be annotated with `@internal`, subject to the cross-platform API policy below. + +#### Cross-platform APIs + +**Open question:** Which approach should the guideline take for conditional-import implementations such as `_*_io.dart` and `_*_web.dart`? The preferred approach is option 2, which keeps platform-specific declarations internal and exposes the supported API through a public facade. + +1. **Exception:** Keep existing declarations as-is and treat conditional-import implementation files as an explicit exception to the rule requiring `@internal` on public top-level declarations in unexported files. +2. **Public facade (preferred):** Annotate declarations in the implementation files with `@internal` and expose the supported API through a public facade that delegates to the selected implementation. For example: + +```dart +// lib/src/widgets/device.dart +import '_device_stub.dart' + if (dart.library.io) '_device_io.dart' + if (dart.library.html) '_device_web.dart' as platform; + +class Device { + Device() : _implementation = platform.DeviceImplementation(); + + final platform.DeviceImplementation _implementation; + + String get name => _implementation.name; + + bool get isAvailable => _implementation.isAvailable; + + Future refresh() => _implementation.refresh(); +} +``` + +```dart +// lib/src/widgets/_device_stub.dart +import 'package:meta/meta.dart'; + +@internal +class DeviceImplementation { + String get name => 'Unknown device'; + + bool get isAvailable => false; + + Future refresh() async {} +} +``` + +```dart +// lib/src/widgets/_device_io.dart +import 'package:meta/meta.dart'; + +@internal +class DeviceImplementation { + String get name => 'Native device'; + + bool get isAvailable => true; + + Future refresh() async { + // Refresh native device state. + } +} +``` + +```dart +// lib/src/widgets/_device_web.dart +import 'package:meta/meta.dart'; + +@internal +class DeviceImplementation { + String get name => 'Web device'; + + bool get isAvailable => true; + + Future refresh() async { + // Refresh web device state. + } +} +``` + +The public barrel exports the facade, not the conditional implementations: + +```dart +export 'src/widgets/device.dart'; +``` + +Under the second approach, consumers use `Device`; the platform-specific implementations remain implementation details. #### Barrel exports and file naming * Drop the requirement that every file in `lib/src//` be exported by `lib/.dart` (superseding [flutter/flutter#108632](https://github.com/flutter/flutter/issues/108632)). -* Any file in `lib/src//` not exported by `lib/.dart` must have a `_` filename prefix (such as `lib/src/widgets/_accessibility_evaluations.dart`), and have all public top-level declarations annotated with `@internal`. +* Any file in `lib/src//` not exported by `lib/.dart` must have a `_` filename prefix (such as `lib/src/widgets/_accessibility_evaluations.dart`), and have all public top-level declarations annotated with `@internal`, subject to the cross-platform API policy above. * When an `@internal` top-level declaration shares a file with public declarations, `lib/.dart` must exclude it with `hide` (such as `export 'src/widgets/foo.dart' hide InternalBar;`). Omitting `hide` in `lib/.dart` triggers [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element). (`hide` is not required on intra-`src/` re-exports.) * Cross-layer imports in `lib/src/` must continue importing top-level barrel files (`package:flutter/.dart`) rather than another layer's `lib/src/` files, with `lib/src/foundation/_features.dart` as the only permitted exception for framework-wide feature flags. #### Enforcement * Accidental public exports of `@internal` declarations are caught by the analyzer's [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element) diagnostic, and external usage is caught by [`invalid_use_of_internal_member`](https://dart.dev/tools/diagnostics/invalid_use_of_internal_member) and `implementation_imports`. -* Repository-specific rules (requiring unexported `lib/src/` files to use a `_` prefix and annotate public top-level declarations with `@internal`, and disallowing `@internal` on members of publicly exported classes) will be enforced via custom analyzer lint rules. +* Repository-specific rules (requiring unexported `lib/src/` files to use a `_` prefix and annotate public top-level declarations with `@internal`, subject to the cross-platform API policy above, and disallowing `@internal` on members of publicly exported classes) will be enforced via custom analyzer lint rules. From 09948ccb6977c2713ce8fcc433a9d28d325030ce Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 21 Oct 2026 15:30:09 -0700 Subject: [PATCH 08/11] update --- rfc/710.0000-style-guide-package-private.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rfc/710.0000-style-guide-package-private.md b/rfc/710.0000-style-guide-package-private.md index 45cda82..2c61854 100644 --- a/rfc/710.0000-style-guide-package-private.md +++ b/rfc/710.0000-style-guide-package-private.md @@ -5,7 +5,7 @@ title: Style guide changes for package private description: Updates the Flutter style guide and breaking change policy for package-private APIs and @internal. status: draft created: 2026-10-06T00:00:00Z -updated: 2026-10-19T22:22:00Z +updated: 2026-10-21T20:02:00Z tags: - 710-style-guide - 030-breaking-changes @@ -55,6 +55,7 @@ Package-private APIs remain discouraged in general, rather than banned. Unexport #### Relationship to `@visibleForTesting` and `@Deprecated` * `@internal` is for package-private declarations omitted or hidden from public barrel exports. +* `@internal` APIs must be used internally. An `@internal` API that is referenced nowhere in the codebase should be removed. * `@internal` must not be used on APIs meant to be removed; use `@Deprecated` instead. * `@visibleForTesting` remains discouraged on public declarations, because declarations exported by a public barrel file remain reachable by external consumers without importing `lib/src/`. @@ -189,3 +190,4 @@ Under the second approach, consumers use `Device`; the platform-specific impleme * Accidental public exports of `@internal` declarations are caught by the analyzer's [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element) diagnostic, and external usage is caught by [`invalid_use_of_internal_member`](https://dart.dev/tools/diagnostics/invalid_use_of_internal_member) and `implementation_imports`. * Repository-specific rules (requiring unexported `lib/src/` files to use a `_` prefix and annotate public top-level declarations with `@internal`, subject to the cross-platform API policy above, and disallowing `@internal` on members of publicly exported classes) will be enforced via custom analyzer lint rules. +* Unused `@internal` declarations not referenced anywhere in the codebase will be caught by a CI check. \ No newline at end of file From 39649c506374143288007ad3d2293a8505c66775 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Wed, 21 Oct 2026 15:38:11 -0700 Subject: [PATCH 09/11] lint --- rfc/710.0000-style-guide-package-private.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/710.0000-style-guide-package-private.md b/rfc/710.0000-style-guide-package-private.md index 2c61854..91e25d1 100644 --- a/rfc/710.0000-style-guide-package-private.md +++ b/rfc/710.0000-style-guide-package-private.md @@ -190,4 +190,4 @@ Under the second approach, consumers use `Device`; the platform-specific impleme * Accidental public exports of `@internal` declarations are caught by the analyzer's [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element) diagnostic, and external usage is caught by [`invalid_use_of_internal_member`](https://dart.dev/tools/diagnostics/invalid_use_of_internal_member) and `implementation_imports`. * Repository-specific rules (requiring unexported `lib/src/` files to use a `_` prefix and annotate public top-level declarations with `@internal`, subject to the cross-platform API policy above, and disallowing `@internal` on members of publicly exported classes) will be enforced via custom analyzer lint rules. -* Unused `@internal` declarations not referenced anywhere in the codebase will be caught by a CI check. \ No newline at end of file +* Unused `@internal` declarations not referenced anywhere in the codebase will be caught by a CI check. From 4c83c57fb8dd044111b97d6b2acac0be2ec5bd03 Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Thu, 22 Oct 2026 13:30:26 -0700 Subject: [PATCH 10/11] update --- rfc/710.0000-style-guide-package-private.md | 22 +++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/rfc/710.0000-style-guide-package-private.md b/rfc/710.0000-style-guide-package-private.md index 91e25d1..eb65ed1 100644 --- a/rfc/710.0000-style-guide-package-private.md +++ b/rfc/710.0000-style-guide-package-private.md @@ -5,7 +5,7 @@ title: Style guide changes for package private description: Updates the Flutter style guide and breaking change policy for package-private APIs and @internal. status: draft created: 2026-10-06T00:00:00Z -updated: 2026-10-21T20:02:00Z +updated: 2026-10-22T19:50:00Z tags: - 710-style-guide - 030-breaking-changes @@ -61,8 +61,8 @@ Package-private APIs remain discouraged in general, rather than banned. Unexport #### Placement rules -* `@internal` may only be applied to top-level declarations (classes, mixins, enums, extensions, typedefs, functions, and variables) inside `lib/src/`. -* Do not apply `@internal` directly to members or constructors of a publicly exported class, because `export ... hide` cannot hide class members. Instead, keep the implementation library-private (`_`) and expose it within the layer via an `@internal` `extension` declared in the same file and excluded from `lib/.dart` with `hide` (an explicit exception to the style guide rule against `extension`s). +* `@internal` may only be applied to declarations inside `lib/src/`. +* For package-private members on a publicly exported class, prefer an `@internal` `extension` declared in the same file, excluded from `lib/.dart` with `hide`, and with both the `extension` and all its members annotated with `@internal` (an explicit exception to the style guide rule against `extension`s). For example, `lib/src/widgets/route_state.dart` can keep the implementation private while allowing another file in the layer to invoke it through an internal extension: @@ -77,6 +77,7 @@ class RouteState { @internal extension RouteStateInternal on RouteState { + @internal void finalizeRoute() => _finalizeRoute(); } ``` @@ -97,6 +98,8 @@ void finish(RouteState state) { } ``` +* When a package-private constructor or class member is needed for inheritance or construction on a publicly exported class, all such constructors and members must be annotated with `@internal`, and the class must be marked `final` or `sealed` so external classes cannot `extends` or `implements` it. +* `@internal` types must not appear in the public signatures or supertypes (`extends`, `implements`, `with`, `on`) of publicly exported declarations, because the analyzer does not flag `@internal` types leaked indirectly through exported APIs. Expose a public type or supertype instead, using an `@internal` constructor if construction must remain package-private. * In any file in `lib/src/` not exported by a barrel file, all public top-level declarations must be annotated with `@internal`, subject to the cross-platform API policy below. #### Cross-platform APIs @@ -188,6 +191,13 @@ Under the second approach, consumers use `Device`; the platform-specific impleme #### Enforcement -* Accidental public exports of `@internal` declarations are caught by the analyzer's [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element) diagnostic, and external usage is caught by [`invalid_use_of_internal_member`](https://dart.dev/tools/diagnostics/invalid_use_of_internal_member) and `implementation_imports`. -* Repository-specific rules (requiring unexported `lib/src/` files to use a `_` prefix and annotate public top-level declarations with `@internal`, subject to the cross-platform API policy above, and disallowing `@internal` on members of publicly exported classes) will be enforced via custom analyzer lint rules. -* Unused `@internal` declarations not referenced anywhere in the codebase will be caught by a CI check. +* Accidental direct exports of `@internal` declarations from barrel files are caught by the analyzer's [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element) diagnostic, and external usage is caught by [`invalid_use_of_internal_member`](https://dart.dev/tools/diagnostics/invalid_use_of_internal_member) and `implementation_imports`. +* Custom analyzer lint rules will enforce single-library repository rules: + * In `_`-prefixed files in `lib/src/`, all public top-level declarations must be annotated with `@internal` (subject to the cross-platform API policy above). + * `@internal` types must not appear in the public signatures or supertypes (`extends`, `implements`, `with`, `on`) of publicly exported declarations. + * Public members of an `@internal` `extension` must also be annotated with `@internal`. + * Any publicly exported class with `@internal` constructors or members must be marked `final` or `sealed`. + * Files in `lib/src//` must not import another layer's `lib/src/` files directly, except for `lib/src/foundation/_features.dart`. +* CI checks will enforce cross-file and barrel rules: + * Any file in `lib/src//` not exported by `lib/.dart` must have a `_` filename prefix, and `_`-prefixed files must not be exported by `lib/.dart`. + * Every `@internal` declaration must be referenced in the codebase; unused `@internal` declarations must be removed. From f40f44710f87d8205604b13e9b87e029a5a1e52b Mon Sep 17 00:00:00 2001 From: Chun-Heng Tai Date: Mon, 2 Nov 2026 11:21:08 -0800 Subject: [PATCH 11/11] update --- rfc/710.0000-style-guide-package-private.md | 41 ++++++++++++--------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/rfc/710.0000-style-guide-package-private.md b/rfc/710.0000-style-guide-package-private.md index eb65ed1..f4c03cb 100644 --- a/rfc/710.0000-style-guide-package-private.md +++ b/rfc/710.0000-style-guide-package-private.md @@ -5,7 +5,7 @@ title: Style guide changes for package private description: Updates the Flutter style guide and breaking change policy for package-private APIs and @internal. status: draft created: 2026-10-06T00:00:00Z -updated: 2026-10-22T19:50:00Z +updated: 2026-11-02T18:30:00Z tags: - 710-style-guide - 030-breaking-changes @@ -25,7 +25,7 @@ Package-private APIs are permitted only for: * Web and I/O conditional imports. * Experimental APIs that are annotated with `@internal` and gated behind a feature flag. -* Internal-facing APIs shared across files within the same layer or accessed by unit tests, when library-private declarations are impractical. +* Internal-facing APIs shared across files within the same layer, when library-private declarations are impractical. ## Context @@ -42,20 +42,21 @@ Relying on filename conventions alone risks accidental leaks. Because `lib/src/` ### Breaking change policy and customer tests -* Modifying, renaming, or removing any declaration in `lib/src/` that is not exported by a public barrel file and is annotated with `@internal` is not a breaking change and requires no deprecation period or announcement. -* Registered tests in [`flutter/tests`](https://github.com/flutter/tests) must not import `package:flutter/src/` or depend on `@internal` APIs. Breakages in customer tests caused by `package:flutter/src/` imports or `@internal` usage are not breaking changes, and contributors may disable or update offending customer tests when modifying `@internal` declarations. +* Modifying, renaming, or removing any declaration in `lib/src/` that is not exported by a public barrel file and is annotated with `@internal` is not a breaking change and requires no deprecation period or announcement, subject to the grace period below. +* Adding `@internal` to an existing declaration must be accompanied by a comment that ends with `This feature was made internal after v.`, where `` is the latest Flutter version tag when the PR is written, following the same convention as `@Deprecated` messages (for example, `// This feature was made internal after v3.50.0-0.3.pre.`). Until a stable release that includes the annotation has shipped, changes to the declaration follow the normal breaking change policy. Declarations that are `@internal` from the start have no grace period. +* Registered tests in [`flutter/tests`](https://github.com/flutter/tests) must not import `package:flutter/src/` or depend on `@internal` APIs. Breakages in customer tests caused by `package:flutter/src/` imports or `@internal` usage are not breaking changes, and contributors may disable or update offending customer tests when modifying `@internal` declarations that are past any grace period. ### Package-private APIs (`@internal`) Package-private APIs remain discouraged in general, rather than banned. Unexported files and package-private declarations in `lib/src/` are permitted only in the following cases: * Experimental APIs that are not yet ready for public stability guarantees. These APIs must be both annotated with `@internal` and gated behind a feature flag. -* Internal-facing APIs shared across files within the same layer (`lib/src//`) or accessed by unit tests. Contributors should use true library-private (`_`) declarations first, and only use `@internal` package-private APIs when the file size gets out of hand or there is a good reason to separate code into multiple files for readability. +* Internal-facing APIs shared across files within the same layer (`lib/src//`). Contributors should use true library-private (`_`) declarations first, and only use `@internal` package-private APIs when keeping the code in one file would put too many distinct concepts in a single file. #### Relationship to `@visibleForTesting` and `@Deprecated` * `@internal` is for package-private declarations omitted or hidden from public barrel exports. -* `@internal` APIs must be used internally. An `@internal` API that is referenced nowhere in the codebase should be removed. +* `@internal` APIs must be used internally. An `@internal` API that is referenced nowhere in the package's non-test code (outside `test/`, `integration_test/`, and other test directories) should be removed. References from tests do not count as uses; test-only access is what `@visibleForTesting` is for. * `@internal` must not be used on APIs meant to be removed; use `@Deprecated` instead. * `@visibleForTesting` remains discouraged on public declarations, because declarations exported by a public barrel file remain reachable by external consumers without importing `lib/src/`. @@ -189,15 +190,19 @@ Under the second approach, consumers use `Device`; the platform-specific impleme * When an `@internal` top-level declaration shares a file with public declarations, `lib/.dart` must exclude it with `hide` (such as `export 'src/widgets/foo.dart' hide InternalBar;`). Omitting `hide` in `lib/.dart` triggers [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element). (`hide` is not required on intra-`src/` re-exports.) * Cross-layer imports in `lib/src/` must continue importing top-level barrel files (`package:flutter/.dart`) rather than another layer's `lib/src/` files, with `lib/src/foundation/_features.dart` as the only permitted exception for framework-wide feature flags. -#### Enforcement - -* Accidental direct exports of `@internal` declarations from barrel files are caught by the analyzer's [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element) diagnostic, and external usage is caught by [`invalid_use_of_internal_member`](https://dart.dev/tools/diagnostics/invalid_use_of_internal_member) and `implementation_imports`. -* Custom analyzer lint rules will enforce single-library repository rules: - * In `_`-prefixed files in `lib/src/`, all public top-level declarations must be annotated with `@internal` (subject to the cross-platform API policy above). - * `@internal` types must not appear in the public signatures or supertypes (`extends`, `implements`, `with`, `on`) of publicly exported declarations. - * Public members of an `@internal` `extension` must also be annotated with `@internal`. - * Any publicly exported class with `@internal` constructors or members must be marked `final` or `sealed`. - * Files in `lib/src//` must not import another layer's `lib/src/` files directly, except for `lib/src/foundation/_features.dart`. -* CI checks will enforce cross-file and barrel rules: - * Any file in `lib/src//` not exported by `lib/.dart` must have a `_` filename prefix, and `_`-prefixed files must not be exported by `lib/.dart`. - * Every `@internal` declaration must be referenced in the codebase; unused `@internal` declarations must be removed. +### TODOs + +The analyzer already catches direct exports of `@internal` declarations from barrel files with [`invalid_export_of_internal_element`](https://dart.dev/tools/diagnostics/invalid_export_of_internal_element), and external usage with [`invalid_use_of_internal_member`](https://dart.dev/tools/diagnostics/invalid_use_of_internal_member) and `implementation_imports`. The remaining work: + +1. Update the [Flutter repository style guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md) and [breaking change policy](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes) to match this proposal, replacing the current ban on package-private APIs and the requirement that every file in `lib/src//` be exported by `lib/.dart`. +2. Add custom analyzer lint rules for single-library repository rules: + * In `_`-prefixed files in `lib/src/`, all public top-level declarations must be annotated with `@internal` (subject to the cross-platform API policy above). + * `@internal` types must not appear in the public signatures or supertypes (`extends`, `implements`, `with`, `on`) of publicly exported declarations. + * Public members of an `@internal` `extension` must also be annotated with `@internal`. + * Any publicly exported class with `@internal` constructors or members must be marked `final` or `sealed`. + * Files in `lib/src//` must not import another layer's `lib/src/` files directly, except for `lib/src/foundation/_features.dart`. +3. Add CI checks for cross-file and barrel rules: + * Any file in `lib/src//` not exported by `lib/.dart` must have a `_` filename prefix, and `_`-prefixed files must not be exported by `lib/.dart`. + * Every `@internal` declaration must be referenced in the package's non-test code; references from tests do not count, and unused `@internal` declarations must be removed. +4. Migrate existing cross-platform code (conditional-import `_*_io.dart` and `_*_web.dart` implementations) to the public facade approach. +5. Audit existing files that contain too many distinct concepts, such as `lib/src/widgets/framework.dart` and `lib/src/widgets/basic.dart`, and split them into smaller files using `@internal` where cross-file access is needed.