From 910cb183024acd92d0f5af66293a29f99bb3250e Mon Sep 17 00:00:00 2001 From: Mairramer Date: Wed, 4 Nov 2026 09:35:44 -0300 Subject: [PATCH 1/5] add RFC 4400000 for extensible enums in federated plugins --- ...0-extensible-enums-in-federated-plugins.md | 432 ++++++++++++++++++ 1 file changed, 432 insertions(+) create mode 100644 rfc/440.0000-extensible-enums-in-federated-plugins.md diff --git a/rfc/440.0000-extensible-enums-in-federated-plugins.md b/rfc/440.0000-extensible-enums-in-federated-plugins.md new file mode 100644 index 0000000..c1d9ede --- /dev/null +++ b/rfc/440.0000-extensible-enums-in-federated-plugins.md @@ -0,0 +1,432 @@ +--- +type: rfc +rfc: '440.0000' +title: Extensible Enums in Federated Plugins +description: Outlines design options and recommendations for handling extensible, enum-like constants in Flutter federated plugins. +status: draft +created: 2026-07-01T00:00:00Z +updated: 2026-07-29T00:00:00Z +tags: + - 440-plugins + - 720-ecosystem +authors: + - https://github.com/Mairramer +--- + +# RFC 440.0000: Extensible Enums in Federated Plugins + +## Overview + +In federated plugins, we often need to expose a set of platform-specific constants (e.g., image formats, barcode types, or sensor configurations). Historically, these have been modeled as standard Dart enums. + +However, standard Dart enums represent a closed set of values. When an underlying operating system (Android, iOS, Web, Windows, macOS, Linux) introduces a new feature and we add a new value to the enum to support it, it introduces a breaking change to the platform interface. Any downstream consumer or platform implementation switching over the enum without a default case fails to compile due to Dart's exhaustiveness checks. + +To avoid requiring a major version bump each time a plugin adds support for a new native capability, we need a standard pattern across Flutter packages that allows these value sets to grow without breaking backward compatibility. + +* **Document Shortlink:** [flutter.dev/go/extensible-enums-plugins](https://flutter.dev/go/extensible-enums-plugins) +* **Audience:** Flutter plugin authors maintaining federated plugins, package contributors, and application developers consuming plugin constants. + +--- + +## Background + +Federated plugins separate concerns between the app-facing package (e.g., `camera`), the common platform interface (`camera_platform_interface`), and platform-specific implementations (`camera_android`, `camera_avfoundation`, `camera_windows`, etc.). + +Because these packages wrap living, evolving operating systems, the set of supported configurations and capabilities inevitably expands over time. + +### Case Study: `camera` and `ImageFormatGroup` + +In the `camera` package (see [flutter/packages#11765](https://github.com/flutter/packages/pull/11765)), adding the `rgba8888` format to `ImageFormatGroup` triggered a breaking change because `ImageFormatGroup` was defined as a standard Dart `enum`. + +The `camera_platform_interface` must be able to safely add new formats as underlying platform APIs evolve. This scenario recurs across numerous federated plugins that interface with evolving native SDKs (e.g., audio, sensors, biometrics, video player), demonstrating the need for a shared, consistent pattern across the Flutter ecosystem. + +### The Two Extensibility Scenarios + +Discussions around extensible sets highlight two distinct use cases that an architectural solution must address: + +1. **Common Platform Interface Evolution:** The shared platform interface package adds newly supported operating system formats or constants over time across multiple platforms. +2. **Platform Implementation Specialization:** An individual platform implementation (e.g., `camera_android`, `camera_windows`, or a community platform package) needs to introduce platform-specific constants that do not apply to other platforms or are not yet represented in the shared platform interface. + +--- + +## Goals and Non-Goals + +### Goals + +1. **Non-breaking Additions:** Prevent compilation breaks for downstream consumers and implementations when new values are introduced. +2. **Platform Specialization:** Allow individual platform implementations to define and pass custom values through the platform interface. +3. **Type Safety & Developer Experience:** Retain compile-time type safety, IDE autocompletion, and clear documentation. +4. **Pattern Matching & Control Flow:** Support clean integration with Dart control flow constructs (such as `switch` expressions and statements). +5. **Serialization:** Support straightforward serialization and deserialization (e.g., to and from JSON or platform channels). + +### Non-Goals + +1. **Deprecating Truly Closed Enums:** Replacing enums that represent naturally closed sets (e.g., values that are fundamentally fixed by domain logic). +2. **Pigeon Serialization Rework:** Replacing Pigeon's internal serialization or fallback mechanisms, which manage their own wire protocol constraints. + +--- + +## Recommendation & Rationale + +### Recommendation + +The recommended approaches for federated plugins are: + +* **Primary Recommendation: Option 7 (Final Class with Properties & Equals/HashCode Override).** Provides the best balance of safety, backward compatibility, and developer ergonomics. It supports standard `switch` statements, simple serialization/deserialization, and allows third parties and platform implementations to instantiate custom constants via constructor or extension. +* **Secondary Recommendation: Option 6 (Class with Properties & Extension Getters).** Suitable when constants must be used as keys in `const` maps or elements in `const` sets, trading `switch` compatibility for `if-else` property matching. +* **Alternative for Dart 3.3+ Packages: Option 2 (Extension Types).** Delivers zero runtime overhead while maintaining an open set of typed constants, suitable for plugins targeting Dart 3.3 or higher. +* **Alternative for Platform Specialization: Option 8 (Base Class as Primitive with Subclasses).** Ideal when platform-specific constants benefit from clear inheritance hierarchy (e.g., `AndroidImageFormatGroup.bgra8888`), making platform provenance explicit at the call site. + +### Rationale + +For federated plugins where constant domains are dictated by underlying operating systems, values cannot be treated as closed sets. + +1. **Open Sets vs. Closed Enums:** Standard Dart enums enforce exhaustiveness. While exhaustiveness is desirable for domain-closed types, it directly conflicts with living API boundaries. Modeling open sets as classes or extension types naturally requires consumers to include a fallback or wildcard branch (`default:` or `_ =>`), preventing downstream build breakage when new constants are added. +2. **The Switch vs. `const` Collection Trade-Off:** + * Overriding `operator ==` and `hashCode` (as in Option 7 and Option 8) enables direct equality in `switch` statements and pattern matching. However, in Dart, overriding equality precludes instances from being used as keys in compile-time `const` maps or elements in `const` sets. + * For plugin configuration constants (such as image formats or capture presets), support for `switch` statements is substantially more common and ergonomically valuable than `const` collection indexing. +3. **`knownValues` vs. `values`:** + * In an extensible or open model, the platform interface cannot know all possible values defined by third-party or platform-specific packages. + * If a list of constants is provided for iteration, it should be named `knownValues` (rather than `values`) to explicitly communicate that the collection reflects only the core values known at compile time to that package. + +--- + +## Options Considered + +Below are the detailed options evaluated to meet the goals outlined above, using `ImageFormatGroup` as a concrete case study. + +### Option 1: Enum with a Private Fallback Value + +Continue using standard Dart enums, but introduce a private or dummy value (e.g., `_fallback` or `_unknown`) to the enum definition. + +```dart +enum ImageFormatGroup { + yuv420, + jpeg, + nv21, + rgba8888, + _fallback, // Forces external users to include a default case +} +``` + +#### Pros (Option 1) + +* Retains standard `enum` syntax and language familiarity. +* **Exhaustiveness workaround:** By intentionally omitting the private dummy value from explicit `case` checks, downstream consumers are forced to provide a `default:` branch, future-proofing their switch statements against new additions. + +#### Cons (Option 1) + +* Calling `ImageFormatGroup.values` includes `_fallback`, potentially corrupting UI builders or iterators that expect only valid domain values. +* Tooling (e.g., dartdoc, IDE autocomplete) may still surface the private member. +* Semantic mismatch: It subverts Dart's type system intent, as enums are explicitly designed for closed sets. +* Does not allow platform implementations or third parties to define custom constants. + +--- + +### Option 2: Extension Types (Dart 3.3+) + +Use Dart 3.3+ `extension type` to wrap an underlying primitive (`String` or `int`), modeling an open set of typed constants with zero runtime overhead. + +```dart +extension type const ImageFormatGroup._(String value) { + static const ImageFormatGroup unknown = ImageFormatGroup._('unknown'); + static const ImageFormatGroup yuv420 = ImageFormatGroup._('yuv420'); + static const ImageFormatGroup rgba8888 = ImageFormatGroup._('rgba8888'); +} + +String getFormatLabel(ImageFormatGroup format) { + return switch (format) { + ImageFormatGroup.unknown => 'Unknown format', + ImageFormatGroup.yuv420 => 'YUV 420', + ImageFormatGroup.rgba8888 => 'RGBA 8888', + _ => 'Default label', + }; +} +``` + +#### Pros (Option 2) + +* **Zero Runtime Overhead:** Compiles down directly to the underlying primitive type at runtime. +* **Non-breaking Additions:** Introducing new `static const` members does not break downstream code. +* **Natural Open Set:** Switch expressions naturally require a fallback (`_ =>`) wildcard. + +#### Cons (Option 2) + +* No built-in `.values` property: Requires manually maintaining a `static const List knownValues`. +* Requires Dart 3.3+, preventing adoption in packages constrained to earlier SDK minimums. +* Less familiar syntax compared to standard classes or enums. + +--- + +### Option 3: Class Types with Static Constants (Dart 3 Modifiers) + +Use a standard Dart class with a private constructor and `static const` instances, paired with Dart 3 class modifiers such as `interface class`. + +```dart +interface class ImageFormatGroup { + const ImageFormatGroup._(); + + static const ImageFormatGroup unknown = ImageFormatGroup._(); + static const ImageFormatGroup yuv420 = ImageFormatGroup._(); + static const ImageFormatGroup rgba8888 = ImageFormatGroup._(); +} +``` + +#### Pros (Option 3) + +* **Non-breaking Additions:** Safe to add new constants over time. +* **Controlled Extensibility:** `interface class` allows third-party implementations to implement the interface (`class CustomFormat implements ImageFormatGroup`). +* Flexible for attaching helper methods and metadata. + +#### Cons (Option 3) + +* **Equality Pitfall:** If all instances share the same parameterless `const` constructor without unique internal fields, equality checks between instances can fail or collapse unless differentiated. +* Increased boilerplate compared to native enums. +* No built-in collection of values. + +--- + +### Option 4: Raw Primitives (Strings / Integers) + +Pass raw primitive values (`String` or `int`) directly across APIs without a wrapper type. + +```dart +const String formatUnknown = 'unknown'; +const String formatYuv420 = 'yuv420'; +const String formatRgba8888 = 'rgba8888'; +``` + +#### Pros (Option 4) + +* Absolute simplicity and maximum runtime performance. +* Infinite extensibility: Any caller can pass any string or integer. + +#### Cons (Option 4) + +* **No Type Safety:** The compiler cannot detect typos (e.g., `'yub420'` instead of `'yuv420'`). +* **Poor Developer Experience:** No IDE autocompletion or method scoping. + +--- + +### Option 5: Open Class Hierarchy (Polymorphism) + +Represent each constant as an individual class implementing a common abstract interface or base class. + +```dart +abstract class ImageFormatGroup {} + +class Yuv420Format implements ImageFormatGroup {} +class Rgba8888Format implements ImageFormatGroup {} +``` + +#### Pros (Option 5) + +* **Non-breaking Additions:** An open (unsealed) class hierarchy requires switch statements to include a fallback case. +* **Payload Support:** Formats can encapsulate complex platform-specific metadata or parameters. + +#### Cons (Option 5) + +* Significant boilerplate and over-engineering for simple domain constants. +* Unwieldy syntax for common constant comparisons. + +--- + +### Option 6: Class with Properties and Extension Getters + +Use a `base class` with a public const constructor, internal property, and boolean getters. Platform packages or external consumers can add custom values using Dart extension methods. + +```dart +base class ImageFormatGroup { + const ImageFormatGroup(this.value); + + final String value; + + static const ImageFormatGroup unknown = ImageFormatGroup('unknown'); + static const ImageFormatGroup yuv420 = ImageFormatGroup('yuv420'); + static const ImageFormatGroup rgba8888 = ImageFormatGroup('rgba8888'); + + bool get isUnknown => value == 'unknown'; + bool get isYuv420 => value == 'yuv420'; + bool get isRgba8888 => value == 'rgba8888'; +} + +// In camera_android or third-party package: +extension CustomImageFormatGroup on ImageFormatGroup { + static const ImageFormatGroup bgra8888 = ImageFormatGroup('bgra8888'); + bool get isBgra8888 => value == 'bgra8888'; +} + +String getFormatLabel(ImageFormatGroup format) { + if (format.isUnknown) { + return 'Unknown format'; + } else if (format.isYuv420) { + return 'YUV 420'; + } else if (format.isRgba8888) { + return 'RGBA 8888'; + } else if (format.isBgra8888) { + return 'BGRA 8888'; + } else { + return 'Default label'; + } +} +``` + +#### Pros (Option 6) + +* Safe, non-breaking additions over time. +* Extensible outside the defining package via extension methods. +* Because equality and hash code are not overridden, `const` instances can be used as keys in `const` maps and elements in `const` sets. +* Intuitive and beginner-friendly, avoiding complex language mechanisms. + +#### Cons (Option 6) + +* Does not support `switch` expressions/statements cleanly; requires `if-else` branching on boolean getters. +* Requires manual boilerplate for getters. + +--- + +### Option 7: Final Class with Properties and Equals/HashCode Override + +Model the extensible constant as a `final class` containing an underlying identifier, a public `fromString` constructor, and explicit `operator ==` and `hashCode` overrides. + +> [!NOTE] +> This pattern is modeled after the approach adopted in [`dart-lang/native#3413`](https://github.com/dart-lang/native/pull/3413) for target operating systems (`OS`) and architectures (`Architecture`). Here, it is adapted to the `ImageFormatGroup` case study. + +```dart +final class ImageFormatGroup { + final String name; + + const ImageFormatGroup._(this.name); + const ImageFormatGroup.fromString(this.name); + + static const ImageFormatGroup unknown = ImageFormatGroup._('unknown'); + static const ImageFormatGroup yuv420 = ImageFormatGroup._('yuv420'); + static const ImageFormatGroup jpeg = ImageFormatGroup._('jpeg'); + static const ImageFormatGroup nv21 = ImageFormatGroup._('nv21'); + static const ImageFormatGroup rgba8888 = ImageFormatGroup._('rgba8888'); + + static const List knownValues = [ + unknown, + yuv420, + jpeg, + nv21, + rgba8888, + ]; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ImageFormatGroup && other.name == name; + + @override + int get hashCode => name.hashCode; + + @override + String toString() => 'ImageFormatGroup.$name'; +} + +// Third-party or platform-specific extension (e.g. in camera_windows): +extension CustomImageFormatGroup on ImageFormatGroup { + static final ImageFormatGroup bgra8888 = + ImageFormatGroup.fromString('bgra8888'); +} + +// Switch statements and expressions work directly: +void handleFormat(ImageFormatGroup format) { + switch (format) { + case ImageFormatGroup.yuv420: + print('Processing YUV 420'); + case ImageFormatGroup.rgba8888: + print('Processing RGBA 8888'); + default: + print('Other format: ${format.name}'); + } +} +``` + +#### Pros (Option 7) + +* **Switch Compatibility:** Supports standard `switch` statements and expressions directly via value equality. +* **Non-breaking Additions:** New constants can be added to the class or via extensions without breaking existing code. +* **Extensible by Third Parties:** Platform packages can define new values using `ImageFormatGroup.fromString(...)`. +* **Serialization Ready:** Trivially serializes to and deserializes from primitive strings (`format.name` and `ImageFormatGroup.fromString(...)`). +* **Idiomatic Dart:** Standard class semantics familiar to all Dart developers. + +#### Cons (Option 7) + +* Cannot be used as keys in compile-time `const` maps or elements in compile-time `const` sets due to overriding `operator ==` and `hashCode`. +* Runtime overhead of small object allocations (mitigated when `const` constructors are utilized). + +--- + +### Option 8: Base Class as Primitive with Subclasses + +Use a `base class` with protected constructor and value equality, allowing individual platform packages to define their own platform-specific subclasses. + +```dart +base class ImageFormatGroup { + final String value; + + @protected + const ImageFormatGroup(this.value); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ImageFormatGroup && value == other.value; + + @override + int get hashCode => value.hashCode; + + static const ImageFormatGroup unknown = ImageFormatGroup('unknown'); + static const ImageFormatGroup yuv420 = ImageFormatGroup('yuv420'); + static const ImageFormatGroup rgba8888 = ImageFormatGroup('rgba8888'); +} + +// In camera_android: +final class AndroidImageFormatGroup extends ImageFormatGroup { + const AndroidImageFormatGroup(super.value); + + static const AndroidImageFormatGroup bgra8888 = + AndroidImageFormatGroup('bgra8888'); +} + +// Optional platform extension to expose base-type getters: +extension AndroidImageFormatExtension on ImageFormatGroup { + bool get isBgra8888 => this == AndroidImageFormatGroup.bgra8888; +} +``` + +#### Pros (Option 8) + +* **Explicit Platform Provenance:** Subclasses make it immediately obvious which platform defines a specific constant (e.g., `AndroidImageFormatGroup.bgra8888`), preventing ambiguity in multi-platform codebases. +* Enables runtime type checks (e.g., `if (format is AndroidImageFormatGroup)`). +* Supports `switch` statements via value equality. + +#### Cons (Option 8) + +* Downstream code typed to the base class cannot access subclass members without either casting, equality comparison (`format == AndroidImageFormatGroup.bgra8888`), or helper extension getters. +* Cannot be used in `const` maps/sets due to `==` override. + +--- + +### Comparison Matrix + +| Option | Breaking Change Safe | `switch` Support | Platform Extensible | Type Safe & Autocomplete | `const` Map/Set Key | Runtime Overhead | +| :--- | :---: | :---: | :---: | :---: | :---: | :---: | +| **Option 1: Enum with Fallback** | ⚠️ Partial | ✅ Yes | ❌ No | ✅ High | ✅ Yes | Minimal | +| **Option 2: Extension Types** | ✅ Yes | ✅ Yes | ⚠️ Via Constructor | ✅ High | ✅ Yes | Zero | +| **Option 3: Interface Class** | ✅ Yes | ❌ Value check | ✅ Implements | ✅ High | ⚠️ Const issue | Low | +| **Option 4: Raw Primitives** | ✅ Yes | ✅ Yes | ✅ Yes | ❌ None | ✅ Yes | Zero | +| **Option 5: Class Hierarchy** | ✅ Yes | ⚠️ Type switch | ✅ Subclass | ✅ High | ❌ No | Moderate | +| **Option 6: Class with Properties** | ✅ Yes | ❌ `if-else` | ✅ Extension | ✅ High | ✅ Yes | Low | +| **Option 7: Final Class with `==`** | ✅ Yes | ✅ Yes | ✅ Yes | ✅ High | ❌ No | Low | +| **Option 8: Base Class + Subclasses** | ✅ Yes | ✅ Yes | ✅ Subclass | ✅ High | ❌ No | Low | + +--- + +## Open Questions + +1. **Dart Team Guidance:** Does the Dart team provide an official recommendation between Extension Types (Option 2) and Class Types (Option 7) for exposing extensible constant domains in public package APIs? +2. **Migration Strategy:** For existing federated plugins that currently expose standard enums (such as `ImageFormatGroup` in `camera_platform_interface`), what is the recommended deprecation and migration path to transition to an extensible class without causing an abrupt breaking change for all existing users? + From 1185b72145f8bfa16fad41c59c1c0a81abf63425 Mon Sep 17 00:00:00 2001 From: Mairramer Date: Wed, 4 Nov 2026 09:47:40 -0300 Subject: [PATCH 2/5] docs: expand RFC 4400 for extensible enums in federated plugins --- ...0-extensible-enums-in-federated-plugins.md | 629 +++++++++++------- 1 file changed, 383 insertions(+), 246 deletions(-) diff --git a/rfc/440.0000-extensible-enums-in-federated-plugins.md b/rfc/440.0000-extensible-enums-in-federated-plugins.md index c1d9ede..33ef23e 100644 --- a/rfc/440.0000-extensible-enums-in-federated-plugins.md +++ b/rfc/440.0000-extensible-enums-in-federated-plugins.md @@ -2,7 +2,7 @@ type: rfc rfc: '440.0000' title: Extensible Enums in Federated Plugins -description: Outlines design options and recommendations for handling extensible, enum-like constants in Flutter federated plugins. +description: Establishes a standard extensible constant pattern for Flutter federated plugins to support growing platform capabilities without breaking backward compatibility. status: draft created: 2026-07-01T00:00:00Z updated: 2026-07-29T00:00:00Z @@ -17,416 +17,553 @@ authors: ## Overview -In federated plugins, we often need to expose a set of platform-specific constants (e.g., image formats, barcode types, or sensor configurations). Historically, these have been modeled as standard Dart enums. +In Flutter federated plugins, packages often expose platform-specific configurations and capabilities (e.g., camera pixel formats, biometric authentication types, sensor modalities, audio/video codecs). Historically, these domain sets have been modeled as standard Dart `enum`s. -However, standard Dart enums represent a closed set of values. When an underlying operating system (Android, iOS, Web, Windows, macOS, Linux) introduces a new feature and we add a new value to the enum to support it, it introduces a breaking change to the platform interface. Any downstream consumer or platform implementation switching over the enum without a default case fails to compile due to Dart's exhaustiveness checks. +However, standard Dart enums represent strictly closed sets. When an underlying operating system (Android, iOS, Web, Windows, macOS, Linux) introduces a new feature and maintainers add a corresponding value to the enum, it triggers a compile-time breaking change for all downstream consumers and platform implementations that switch over the enum without a wildcard or `default` branch. -To avoid requiring a major version bump each time a plugin adds support for a new native capability, we need a standard pattern across Flutter packages that allows these value sets to grow without breaking backward compatibility. +To enable the Flutter ecosystem to evolve rapidly alongside living operating systems without forcing frequent major semantic version bumps, this RFC establishes **The Extensible Constant Pattern**: a standardized, backward-compatible, type-safe pattern for open domain sets across Flutter packages. * **Document Shortlink:** [flutter.dev/go/extensible-enums-plugins](https://flutter.dev/go/extensible-enums-plugins) -* **Audience:** Flutter plugin authors maintaining federated plugins, package contributors, and application developers consuming plugin constants. +* **Tracking Issue:** [flutter/flutter#189696](https://github.com/flutter/flutter/issues/189696) +* **Audience:** Flutter plugin authors, platform interface maintainers, and application developers consuming platform constants. --- -## Background +## Table of Contents + +- [Overview](#overview) +- [1. Problem Statement and Motivation](#1-problem-statement-and-motivation) + - [1.1 The Incompatibility: Closed Enums vs. Living Operating Systems](#11-the-incompatibility-closed-enums-vs-living-operating-systems) + - [1.2 Exhaustiveness and Breaking Changes in Dart 3](#12-exhaustiveness-and-breaking-changes-in-dart-3) + - [1.3 The Two Extensibility Dimensions](#13-the-two-extensibility-dimensions) + - [1.4 Historical Context and Prior Art](#14-historical-context-and-prior-art) +- [2. Design Goals and Principles](#2-design-goals-and-principles) + - [2.1 Goals](#21-goals) + - [2.2 Non-Goals](#22-non-goals) + - [2.3 Decision Guide: Standard Enum vs. Extensible Constant Class](#23-decision-guide-standard-enum-vs-extensible-constant-class) +- [3. The Extensible Constant Pattern Specification](#3-the-extensible-constant-pattern-specification) + - [3.1 Canonical Class Anatomy](#31-canonical-class-anatomy) + - [3.2 Value Equality and `switch` Compatibility](#32-value-equality-and-switch-compatibility) + - [3.3 Discovery via `knownValues` vs. `values`](#33-discovery-via-knownvalues-vs-values) + - [3.4 Platform Specialization Patterns](#34-platform-specialization-patterns) + - [3.5 Serialization and Wire Protocols](#35-serialization-and-wire-protocols) +- [4. Primary Case Study: `camera` and `ImageFormatGroup`](#4-primary-case-study-camera-and-imageformatgroup) + - [4.1 The Breaking Incident (PR #11765)](#41-the-breaking-incident-pr-11765) + - [4.2 Applying the Extensible Constant Pattern](#42-applying-the-extensible-constant-pattern) + - [4.3 Consumer and Platform Implementation Ergonomics](#43-consumer-and-platform-implementation-ergonomics) +- [5. Broader Ecosystem Applications](#5-broader-ecosystem-applications) + - [5.1 Biometric Authentication (`local_auth`)](#51-biometric-authentication-local_auth) + - [5.2 Media Formats and Codecs (`video_player` / `audioplayers`)](#52-media-formats-and-codecs-video_player--audioplayers) + - [5.3 Hardware Sensors (`sensors_plus`)](#53-hardware-sensors-sensors_plus) + - [5.4 Barcode & QR Formats (`mobile_scanner`)](#54-barcode--qr-formats-mobile_scanner) +- [6. Options Considered](#6-options-considered) + - [Option 1: Enum with a Private Fallback Value](#option-1-enum-with-a-private-fallback-value) + - [Option 2: Extension Types (Dart 3.3+)](#option-2-extension-types-dart-33) + - [Option 3: Interface Class with Static Constants](#option-3-interface-class-with-static-constants) + - [Option 4: Raw Primitives (Strings / Integers)](#option-4-raw-primitives-strings--integers) + - [Option 5: Open Class Hierarchy (Polymorphism)](#option-5-open-class-hierarchy-polymorphism) + - [Option 6: Class with Properties and Extension Getters](#option-6-class-with-properties-and-extension-getters) + - [Option 7: Final Class with Equals/HashCode Override (Adopted Pattern)](#option-7-final-class-with-equalshashcode-override-adopted-pattern) + - [Option 8: Base Class as Primitive with Subclasses](#option-8-base-class-as-primitive-with-subclasses) + - [Comprehensive Comparison Matrix](#comprehensive-comparison-matrix) +- [7. Migration Strategies and Open Questions](#7-migration-strategies-and-open-questions) + - [7.1 Migration Path for Existing Packages](#71-migration-path-for-existing-packages) + - [7.2 Open Questions](#72-open-questions) -Federated plugins separate concerns between the app-facing package (e.g., `camera`), the common platform interface (`camera_platform_interface`), and platform-specific implementations (`camera_android`, `camera_avfoundation`, `camera_windows`, etc.). +--- -Because these packages wrap living, evolving operating systems, the set of supported configurations and capabilities inevitably expands over time. +## 1. Problem Statement and Motivation -### Case Study: `camera` and `ImageFormatGroup` +### 1.1 The Incompatibility: Closed Enums vs. Living Operating Systems -In the `camera` package (see [flutter/packages#11765](https://github.com/flutter/packages/pull/11765)), adding the `rgba8888` format to `ImageFormatGroup` triggered a breaking change because `ImageFormatGroup` was defined as a standard Dart `enum`. +Federated plugins decouple an application-facing package from its platform-specific implementations via a shared platform interface package. While the Dart programming language models `enum`s as finite, exhaustive sets, host operating systems are inherently open and continuously evolving. -The `camera_platform_interface` must be able to safely add new formats as underlying platform APIs evolve. This scenario recurs across numerous federated plugins that interface with evolving native SDKs (e.g., audio, sensors, biometrics, video player), demonstrating the need for a shared, consistent pattern across the Flutter ecosystem. +Each annual OS release (e.g., Android API bumps, iOS updates, Windows SDKs, Web standards) exposes new hardware capabilities, formats, and configurations. When a plugin wraps native APIs whose domains expand over time, treating the domain as a closed set fundamentally conflicts with the underlying platform lifecycle. -### The Two Extensibility Scenarios +### 1.2 Exhaustiveness and Breaking Changes in Dart 3 -Discussions around extensible sets highlight two distinct use cases that an architectural solution must address: +Dart 3 enhanced pattern matching and exhaustiveness checking for `switch` statements and expressions. If an application developer writes: -1. **Common Platform Interface Evolution:** The shared platform interface package adds newly supported operating system formats or constants over time across multiple platforms. -2. **Platform Implementation Specialization:** An individual platform implementation (e.g., `camera_android`, `camera_windows`, or a community platform package) needs to introduce platform-specific constants that do not apply to other platforms or are not yet represented in the shared platform interface. +```dart +// Consumer code switching over an enum without a default branch: +String formatName = switch (cameraFormat) { + ImageFormatGroup.yuv420 => 'YUV 420', + ImageFormatGroup.jpeg => 'JPEG', + ImageFormatGroup.nv21 => 'NV21', +}; +``` ---- +When the platform interface adds a single new enum value (e.g., `ImageFormatGroup.rgba8888`), Dart's compiler issues an error on the consumer's switch: + +```text +The type 'ImageFormatGroup' is not exhaustively matched by the switch cases. +Missing cases: +- ImageFormatGroup.rgba8888 +``` + +Because adding an enum value breaks downstream compilation, every addition technically constitutes a breaking change under Semantic Versioning (SemVer), necessitating a major version bump (`1.x.x` $\rightarrow$ `2.0.0`) of the platform interface and all federated packages that depend on it. -## Goals and Non-Goals +### 1.3 The Two Extensibility Dimensions -### Goals +Architectural discussions across `flutter/packages` surface two distinct extensibility dimensions that a unified solution must accommodate: -1. **Non-breaking Additions:** Prevent compilation breaks for downstream consumers and implementations when new values are introduced. -2. **Platform Specialization:** Allow individual platform implementations to define and pass custom values through the platform interface. -3. **Type Safety & Developer Experience:** Retain compile-time type safety, IDE autocompletion, and clear documentation. -4. **Pattern Matching & Control Flow:** Support clean integration with Dart control flow constructs (such as `switch` expressions and statements). -5. **Serialization:** Support straightforward serialization and deserialization (e.g., to and from JSON or platform channels). +1. **Common Platform Interface Evolution (Horizontal):** The core platform interface package introduces new constants supported across multiple platforms over time (e.g., adding a format common to newer Android and iOS SDKs). +2. **Platform Implementation Specialization (Vertical):** A specific platform implementation (e.g., `camera_android`, `camera_windows`, or a community desktop package) needs to introduce platform-specific constants that do not apply to other platforms or are not yet represented in the shared platform interface. -### Non-Goals +### 1.4 Historical Context and Prior Art -1. **Deprecating Truly Closed Enums:** Replacing enums that represent naturally closed sets (e.g., values that are fundamentally fixed by domain logic). -2. **Pigeon Serialization Rework:** Replacing Pigeon's internal serialization or fallback mechanisms, which manage their own wire protocol constraints. +* **Flutter Packages Issue (PR [#11765](https://github.com/flutter/packages/pull/11765)):** Adding `rgba8888` to `ImageFormatGroup` in `camera_platform_interface` triggered a breaking change across dependent packages, precipitating this RFC. +* **Dart Native Systems (`dart-lang/native#3413`):** The Dart Native and FFI team confronted this exact challenge when modeling host operating systems (`OS`) and CPU architectures (`Architecture`). Rather than using an `enum`, they adopted a `final class` pattern with `fromString` constructors and value equality, allowing third-party platforms to extend the system safely. --- -## Recommendation & Rationale +## 2. Design Goals and Principles -### Recommendation +### 2.1 Goals -The recommended approaches for federated plugins are: +1. **Non-breaking Additions:** Adding new constants to the platform interface must not break compilation for existing consumers or implementations. +2. **Platform Specialization:** Allow individual platform packages to define and forward custom, platform-specific constants through the shared interface. +3. **Type Safety & Ergonomics:** Maintain compile-time type safety, strong typing, namespace scoping, and IDE autocompletion. +4. **Pattern Matching & Control Flow:** Ensure clean integration with Dart 3 `switch` statements and switch expressions without runtime overhead. +5. **Serialization:** Support trivial serialization to and deserialization from primitive strings for JSON and platform channel messaging. -* **Primary Recommendation: Option 7 (Final Class with Properties & Equals/HashCode Override).** Provides the best balance of safety, backward compatibility, and developer ergonomics. It supports standard `switch` statements, simple serialization/deserialization, and allows third parties and platform implementations to instantiate custom constants via constructor or extension. -* **Secondary Recommendation: Option 6 (Class with Properties & Extension Getters).** Suitable when constants must be used as keys in `const` maps or elements in `const` sets, trading `switch` compatibility for `if-else` property matching. -* **Alternative for Dart 3.3+ Packages: Option 2 (Extension Types).** Delivers zero runtime overhead while maintaining an open set of typed constants, suitable for plugins targeting Dart 3.3 or higher. -* **Alternative for Platform Specialization: Option 8 (Base Class as Primitive with Subclasses).** Ideal when platform-specific constants benefit from clear inheritance hierarchy (e.g., `AndroidImageFormatGroup.bgra8888`), making platform provenance explicit at the call site. +### 2.2 Non-Goals -### Rationale +1. **Deprecating Genuinely Closed Enums:** Replacing enums that represent fixed domain logic (e.g., boolean-like states or cardinal orientations). +2. **Reworking Pigeon Code Generation:** Replacing Pigeon's internal wire protocol serializers, which handle their own code-generated fallback rules. -For federated plugins where constant domains are dictated by underlying operating systems, values cannot be treated as closed sets. +### 2.3 Decision Guide: Standard Enum vs. Extensible Constant Class -1. **Open Sets vs. Closed Enums:** Standard Dart enums enforce exhaustiveness. While exhaustiveness is desirable for domain-closed types, it directly conflicts with living API boundaries. Modeling open sets as classes or extension types naturally requires consumers to include a fallback or wildcard branch (`default:` or `_ =>`), preventing downstream build breakage when new constants are added. -2. **The Switch vs. `const` Collection Trade-Off:** - * Overriding `operator ==` and `hashCode` (as in Option 7 and Option 8) enables direct equality in `switch` statements and pattern matching. However, in Dart, overriding equality precludes instances from being used as keys in compile-time `const` maps or elements in `const` sets. - * For plugin configuration constants (such as image formats or capture presets), support for `switch` statements is substantially more common and ergonomically valuable than `const` collection indexing. -3. **`knownValues` vs. `values`:** - * In an extensible or open model, the platform interface cannot know all possible values defined by third-party or platform-specific packages. - * If a list of constants is provided for iteration, it should be named `knownValues` (rather than `values`) to explicitly communicate that the collection reflects only the core values known at compile time to that package. +To guide package authors, this RFC establishes a clear decision framework: ---- +| Domain Characteristic | Use Standard Dart `enum` | Use Extensible Constant Pattern | +| :--- | :---: | :---: | +| **Domain Lifecycle** | Strictly closed and mathematically or logically bounded (e.g., `CameraLensDirection`: `front`, `back`, `external`). | Open and dictated by host OS features (e.g., pixel formats, codecs, sensor types). | +| **Platform Additions Expected** | Unlikely to ever change without a complete architectural redesign. | Expected to grow as mobile/desktop OSs add capabilities. | +| **Platform-Specific Subtypes** | No platform can define bespoke values. | Platforms need bespoke values (e.g., Windows BGRA, Android YV12). | +| **Exhaustiveness Requirement** | Desirable: compiler must guarantee every single case is handled. | Anti-pattern: consumers must be forced to provide a fallback/default branch. | -## Options Considered +--- -Below are the detailed options evaluated to meet the goals outlined above, using `ImageFormatGroup` as a concrete case study. +## 3. The Extensible Constant Pattern Specification -### Option 1: Enum with a Private Fallback Value +### 3.1 Canonical Class Anatomy -Continue using standard Dart enums, but introduce a private or dummy value (e.g., `_fallback` or `_unknown`) to the enum definition. +The recommended standard for extensible constants is a `final class` that wraps a primitive `name` identifier, with private and public named constructors, value equality, and static constant definitions: ```dart -enum ImageFormatGroup { - yuv420, - jpeg, - nv21, - rgba8888, - _fallback, // Forces external users to include a default case -} -``` +/// Represents an extensible set of constants. +final class ExtensibleConstant { + /// The underlying string identifier of this constant. + final String name; -#### Pros (Option 1) + // Private constructor for core constants defined within the package. + const ExtensibleConstant._(this.name); + + /// Creates a constant from an arbitrary string value. + /// + /// Used by platform implementations and application code to instantiate + /// custom or platform-specific constants. + const ExtensibleConstant.fromString(this.name); + + // Core predefined constants: + static const ExtensibleConstant firstOption = + ExtensibleConstant._('firstOption'); + static const ExtensibleConstant secondOption = + ExtensibleConstant._('secondOption'); + + /// Predefined constants known at compile time to this package. + /// + /// Does not include custom or third-party platform constants. + static const List knownValues = [ + firstOption, + secondOption, + ]; -* Retains standard `enum` syntax and language familiarity. -* **Exhaustiveness workaround:** By intentionally omitting the private dummy value from explicit `case` checks, downstream consumers are forced to provide a `default:` branch, future-proofing their switch statements against new additions. + @override + bool operator ==(Object other) => + identical(this, other) || + other is ExtensibleConstant && other.name == name; -#### Cons (Option 1) + @override + int get hashCode => name.hashCode; -* Calling `ImageFormatGroup.values` includes `_fallback`, potentially corrupting UI builders or iterators that expect only valid domain values. -* Tooling (e.g., dartdoc, IDE autocomplete) may still surface the private member. -* Semantic mismatch: It subverts Dart's type system intent, as enums are explicitly designed for closed sets. -* Does not allow platform implementations or third parties to define custom constants. + @override + String toString() => 'ExtensibleConstant.$name'; +} +``` ---- +### 3.2 Value Equality and `switch` Compatibility -### Option 2: Extension Types (Dart 3.3+) +Because `operator ==` and `hashCode` are overridden based on `name`: -Use Dart 3.3+ `extension type` to wrap an underlying primitive (`String` or `int`), modeling an open set of typed constants with zero runtime overhead. +1. Two instances with matching `name` strings evaluate as equal (`identical` fast-path ensures zero overhead for static `const` instances). +2. Dart 3 `switch` statements and expressions match instances by equality cleanly. +3. Because the class is an open type (not an `enum` or `sealed class`), the Dart compiler naturally requires a wildcard pattern (`_ =>`) or `default:` clause, eliminating future compilation breaks when new constants are added. ```dart -extension type const ImageFormatGroup._(String value) { - static const ImageFormatGroup unknown = ImageFormatGroup._('unknown'); - static const ImageFormatGroup yuv420 = ImageFormatGroup._('yuv420'); - static const ImageFormatGroup rgba8888 = ImageFormatGroup._('rgba8888'); -} - -String getFormatLabel(ImageFormatGroup format) { - return switch (format) { - ImageFormatGroup.unknown => 'Unknown format', - ImageFormatGroup.yuv420 => 'YUV 420', - ImageFormatGroup.rgba8888 => 'RGBA 8888', - _ => 'Default label', +String handleConstant(ExtensibleConstant value) { + return switch (value) { + ExtensibleConstant.firstOption => 'Handled First Option', + ExtensibleConstant.secondOption => 'Handled Second Option', + _ => 'Handled fallback: ${value.name}', }; } ``` -#### Pros (Option 2) +### 3.3 Discovery via `knownValues` vs. `values` -* **Zero Runtime Overhead:** Compiles down directly to the underlying primitive type at runtime. -* **Non-breaking Additions:** Introducing new `static const` members does not break downstream code. -* **Natural Open Set:** Switch expressions naturally require a fallback (`_ =>`) wildcard. +Standard enums expose a built-in `.values` property. In an extensible domain, the core package cannot know about values instantiated at runtime or in separate packages. -#### Cons (Option 2) +Providing a static list named `values` creates an illusion of exhaustiveness that leads to runtime defects (e.g., if a UI loop assumes `.values` contains all valid options). -* No built-in `.values` property: Requires manually maintaining a `static const List knownValues`. -* Requires Dart 3.3+, preventing adoption in packages constrained to earlier SDK minimums. -* Less familiar syntax compared to standard classes or enums. +* Packages adopting this pattern **MUST NOT** name their discovery list `values`. +* Packages **SHOULD** name the collection `knownValues` to explicitly signal that it represents only the constants known to the core package at compile time. ---- +### 3.4 Platform Specialization Patterns + +When a platform implementation needs to introduce bespoke constants, it can choose between two ergonomic patterns: -### Option 3: Class Types with Static Constants (Dart 3 Modifiers) +#### Pattern A: Extension Methods (Recommended for most plugins) -Use a standard Dart class with a private constructor and `static const` instances, paired with Dart 3 class modifiers such as `interface class`. +The platform package defines an `extension` on the core class, exposing static constants or getters: ```dart -interface class ImageFormatGroup { - const ImageFormatGroup._(); +// In package:camera_windows/camera_windows.dart: +extension WindowsImageFormats on ImageFormatGroup { + /// Windows-specific BGRA format. + static final ImageFormatGroup bgra8888 = + ImageFormatGroup.fromString('bgra8888'); +} +``` - static const ImageFormatGroup unknown = ImageFormatGroup._(); - static const ImageFormatGroup yuv420 = ImageFormatGroup._(); - static const ImageFormatGroup rgba8888 = ImageFormatGroup._(); +#### Pattern B: Platform Subclassing (When platform provenance is paramount) + +If the platform interface uses `base class` instead of `final class` (Option 8), platform implementations can define a distinct subclass: + +```dart +// In package:camera_android/camera_android.dart: +final class AndroidImageFormatGroup extends ImageFormatGroup { + const AndroidImageFormatGroup(super.value); + + static const AndroidImageFormatGroup bgra8888 = + AndroidImageFormatGroup('bgra8888'); } ``` -#### Pros (Option 3) +This pattern makes platform origin explicit in code (`AndroidImageFormatGroup.bgra8888`) and allows runtime type guards (`if (format is AndroidImageFormatGroup)`). -* **Non-breaking Additions:** Safe to add new constants over time. -* **Controlled Extensibility:** `interface class` allows third-party implementations to implement the interface (`class CustomFormat implements ImageFormatGroup`). -* Flexible for attaching helper methods and metadata. +### 3.5 Serialization and Wire Protocols -#### Cons (Option 3) +Because instances are backed by a primitive `name` string: -* **Equality Pitfall:** If all instances share the same parameterless `const` constructor without unique internal fields, equality checks between instances can fail or collapse unless differentiated. -* Increased boilerplate compared to native enums. -* No built-in collection of values. +* **Serialization:** Passing across platform channels or JSON payloads requires only reading `instance.name`. +* **Deserialization:** Reconstructing an instance from native messages uses `ExtensibleConstant.fromString(rawString)`. Unknown strings sent from newer native OS versions deserialize into valid instances without failing. --- -### Option 4: Raw Primitives (Strings / Integers) +## 4. Primary Case Study: `camera` and `ImageFormatGroup` + +### 4.1 The Breaking Incident (PR #11765) -Pass raw primitive values (`String` or `int`) directly across APIs without a wrapper type. +In `camera_platform_interface`, `ImageFormatGroup` was originally defined as: ```dart -const String formatUnknown = 'unknown'; -const String formatYuv420 = 'yuv420'; -const String formatRgba8888 = 'rgba8888'; +// Legacy definition in camera_platform_interface: +enum ImageFormatGroup { + unknown, + yuv420, + jpeg, + nv21, +} ``` -#### Pros (Option 4) +When PR [#11765](https://github.com/flutter/packages/pull/11765) added `rgba8888` to support modern camera capture pipelines, consumer apps and test suites (such as `android_camera_camerax_test`) immediately broke because their `switch` statements did not account for `rgba8888`. -* Absolute simplicity and maximum runtime performance. -* Infinite extensibility: Any caller can pass any string or integer. +### 4.2 Applying the Extensible Constant Pattern -#### Cons (Option 4) +Migrating `ImageFormatGroup` to the Extensible Constant Pattern resolves the issue: -* **No Type Safety:** The compiler cannot detect typos (e.g., `'yub420'` instead of `'yuv420'`). -* **Poor Developer Experience:** No IDE autocompletion or method scoping. +```dart +final class ImageFormatGroup { + /// The string identifier of this format. + final String name; ---- + const ImageFormatGroup._(this.name); -### Option 5: Open Class Hierarchy (Polymorphism) + /// Creates a custom or platform-specific image format group. + const ImageFormatGroup.fromString(this.name); -Represent each constant as an individual class implementing a common abstract interface or base class. + static const ImageFormatGroup unknown = ImageFormatGroup._('unknown'); + static const ImageFormatGroup yuv420 = ImageFormatGroup._('yuv420'); + static const ImageFormatGroup jpeg = ImageFormatGroup._('jpeg'); + static const ImageFormatGroup nv21 = ImageFormatGroup._('nv21'); + static const ImageFormatGroup rgba8888 = ImageFormatGroup._('rgba8888'); -```dart -abstract class ImageFormatGroup {} + /// Formats known at compile time to camera_platform_interface. + static const List knownValues = [ + unknown, + yuv420, + jpeg, + nv21, + rgba8888, + ]; -class Yuv420Format implements ImageFormatGroup {} -class Rgba8888Format implements ImageFormatGroup {} + @override + bool operator ==(Object other) => + identical(this, other) || + other is ImageFormatGroup && other.name == name; + + @override + int get hashCode => name.hashCode; + + @override + String toString() => 'ImageFormatGroup.$name'; +} ``` -#### Pros (Option 5) +### 4.3 Consumer and Platform Implementation Ergonomics -* **Non-breaking Additions:** An open (unsealed) class hierarchy requires switch statements to include a fallback case. -* **Payload Support:** Formats can encapsulate complex platform-specific metadata or parameters. +* **Consumer Switches:** Existing switch statements continue to work cleanly by adding a default branch: -#### Cons (Option 5) + ```dart + String formatDescription(ImageFormatGroup format) { + return switch (format) { + ImageFormatGroup.yuv420 => 'YUV 420 for live preview streaming', + ImageFormatGroup.jpeg => 'Standard compressed photographic capture', + ImageFormatGroup.rgba8888 => 'Raw 32-bit pixel buffer for compute', + _ => 'Platform-specific or custom format (${format.name})', + }; + } + ``` -* Significant boilerplate and over-engineering for simple domain constants. -* Unwieldy syntax for common constant comparisons. +* **Adding Future Formats:** When iOS adds a new ProRes RAW pixel format or Android adds a specialized depth buffer format, `camera_platform_interface` can add `static const ImageFormatGroup proResRaw` in a minor (`4.x.0`) release with zero risk of breaking downstream code. --- -### Option 6: Class with Properties and Extension Getters +## 5. Broader Ecosystem Applications -Use a `base class` with a public const constructor, internal property, and boolean getters. Platform packages or external consumers can add custom values using Dart extension methods. +The challenges resolved in `camera` recur across multiple first-party and community federated plugins. Adopting this standard provides immediate benefits across the ecosystem: -```dart -base class ImageFormatGroup { - const ImageFormatGroup(this.value); +### 5.1 Biometric Authentication (`local_auth`) - final String value; +`local_auth` currently models biometric modalities as `enum BiometricType { face, fingerprint, iris, weak, strong }`. As wearable and ambient platforms introduce vein scanning, palm matching, or combined passive modalities, `BiometricType` should adopt the extensible constant pattern: - static const ImageFormatGroup unknown = ImageFormatGroup('unknown'); - static const ImageFormatGroup yuv420 = ImageFormatGroup('yuv420'); - static const ImageFormatGroup rgba8888 = ImageFormatGroup('rgba8888'); +```dart +final class BiometricType { + final String name; + const BiometricType._(this.name); + const BiometricType.fromString(this.name); + + static const BiometricType face = BiometricType._('face'); + static const BiometricType fingerprint = BiometricType._('fingerprint'); + static const BiometricType iris = BiometricType._('iris'); + static const BiometricType weak = BiometricType._('weak'); + static const BiometricType strong = BiometricType._('strong'); + + static const List knownValues = [ + face, + fingerprint, + iris, + weak, + strong, + ]; - bool get isUnknown => value == 'unknown'; - bool get isYuv420 => value == 'yuv420'; - bool get isRgba8888 => value == 'rgba8888'; -} + @override + bool operator ==(Object other) => + identical(this, other) || other is BiometricType && other.name == name; -// In camera_android or third-party package: -extension CustomImageFormatGroup on ImageFormatGroup { - static const ImageFormatGroup bgra8888 = ImageFormatGroup('bgra8888'); - bool get isBgra8888 => value == 'bgra8888'; -} + @override + int get hashCode => name.hashCode; -String getFormatLabel(ImageFormatGroup format) { - if (format.isUnknown) { - return 'Unknown format'; - } else if (format.isYuv420) { - return 'YUV 420'; - } else if (format.isRgba8888) { - return 'RGBA 8888'; - } else if (format.isBgra8888) { - return 'BGRA 8888'; - } else { - return 'Default label'; - } + @override + String toString() => 'BiometricType.$name'; } ``` -#### Pros (Option 6) +### 5.2 Media Formats and Codecs (`video_player` / `audioplayers`) -* Safe, non-breaking additions over time. -* Extensible outside the defining package via extension methods. -* Because equality and hash code are not overridden, `const` instances can be used as keys in `const` maps and elements in `const` sets. -* Intuitive and beginner-friendly, avoiding complex language mechanisms. +Video player plugins continuously adapt to new HDR profiles (Dolby Vision, HDR10+, AV1, HLG) and hardware surface decoders. Modeling decoder formats as extensible constants prevents client breakage when hardware acceleration backends introduce modern profile identifiers. -#### Cons (Option 6) +### 5.3 Hardware Sensors (`sensors_plus`) -* Does not support `switch` expressions/statements cleanly; requires `if-else` branching on boolean getters. -* Requires manual boilerplate for getters. +Sensors on modern mobile and automotive devices include step counters, ambient temperature, heart rate, geomagnetic rotation vectors, and lidar depth sensors. Using extensible constants allows platform packages to pass device-specific sensor feeds without blocking on platform interface updates. + +### 5.4 Barcode & QR Formats (`mobile_scanner`) + +Native MLKit and Apple Vision APIs add specialized barcode standards (e.g., MicroQR, GS1 DataBar, Aztec). An extensible format class ensures applications can consume newly supported formats the day an underlying OS SDK ships. --- -### Option 7: Final Class with Properties and Equals/HashCode Override +## 6. Options Considered -Model the extensible constant as a `final class` containing an underlying identifier, a public `fromString` constructor, and explicit `operator ==` and `hashCode` overrides. +### Option 1: Enum with a Private Fallback Value -> [!NOTE] -> This pattern is modeled after the approach adopted in [`dart-lang/native#3413`](https://github.com/dart-lang/native/pull/3413) for target operating systems (`OS`) and architectures (`Architecture`). Here, it is adapted to the `ImageFormatGroup` case study. +Add a private sentinel member (e.g., `_fallback`) to a standard enum to force consumers to write a `default:` case. ```dart -final class ImageFormatGroup { - final String name; +enum ImageFormatGroup { + yuv420, + jpeg, + nv21, + rgba8888, + _fallback, +} +``` - const ImageFormatGroup._(this.name); - const ImageFormatGroup.fromString(this.name); +* **Pros:** Familiar syntax; forces `default` branches in consumer code. +* **Cons:** `ImageFormatGroup.values` includes `_fallback`, polluting UI iterations; does not allow third-party or platform implementations to instantiate custom constants; subverts Dart's type semantics. + +--- + +### Option 2: Extension Types (Dart 3.3+) + +Wrap a primitive `String` with an `extension type`: +```dart +extension type const ImageFormatGroup._(String value) { static const ImageFormatGroup unknown = ImageFormatGroup._('unknown'); static const ImageFormatGroup yuv420 = ImageFormatGroup._('yuv420'); - static const ImageFormatGroup jpeg = ImageFormatGroup._('jpeg'); - static const ImageFormatGroup nv21 = ImageFormatGroup._('nv21'); static const ImageFormatGroup rgba8888 = ImageFormatGroup._('rgba8888'); +} +``` - static const List knownValues = [ - unknown, - yuv420, - jpeg, - nv21, - rgba8888, - ]; +* **Pros:** Zero runtime overhead (erased to `String` at runtime); non-breaking constant additions; natural switch fallback requirement. +* **Cons:** No built-in list discovery; raises minimum SDK constraint to Dart 3.3+; unfamiliar ergonomics for junior contributors. - @override - bool operator ==(Object other) => - identical(this, other) || - other is ImageFormatGroup && other.name == name; +--- - @override - int get hashCode => name.hashCode; +### Option 3: Interface Class with Static Constants - @override - String toString() => 'ImageFormatGroup.$name'; -} +Use `interface class` with static `const` instances and a private constructor: -// Third-party or platform-specific extension (e.g. in camera_windows): -extension CustomImageFormatGroup on ImageFormatGroup { - static final ImageFormatGroup bgra8888 = - ImageFormatGroup.fromString('bgra8888'); +```dart +interface class ImageFormatGroup { + const ImageFormatGroup._(); + static const ImageFormatGroup unknown = ImageFormatGroup._(); + static const ImageFormatGroup yuv420 = ImageFormatGroup._(); } +``` -// Switch statements and expressions work directly: -void handleFormat(ImageFormatGroup format) { - switch (format) { - case ImageFormatGroup.yuv420: - print('Processing YUV 420'); - case ImageFormatGroup.rgba8888: - print('Processing RGBA 8888'); - default: - print('Other format: ${format.name}'); - } +* **Pros:** Non-breaking additions; allows third parties to `implement` the class. +* **Cons:** Instances sharing a parameterless constructor evaluate as identical, breaking distinct equality unless internal fields are added; verbose boilerplate. + +--- + +### Option 4: Raw Primitives (Strings / Integers) + +Pass unencapsulated `String` or `int` constants across APIs: + +```dart +const String formatYuv420 = 'yuv420'; +``` + +* **Pros:** Utmost simplicity; zero overhead. +* **Cons:** Zero compile-time type safety; prone to typos; poor autocompletion and documentation discovery. + +--- + +### Option 5: Open Class Hierarchy (Polymorphism) + +Represent each constant as a discrete class implementing an unsealed interface: + +```dart +abstract class ImageFormatGroup {} +class Yuv420Format implements ImageFormatGroup {} +``` + +* **Pros:** Non-breaking additions; formats can carry complex payloads. +* **Cons:** Excessive boilerplate and over-engineering for simple domain tags. + +--- + +### Option 6: Class with Properties and Extension Getters + +Use a `base class` without equality overrides, testing values via boolean getters: + +```dart +base class ImageFormatGroup { + const ImageFormatGroup(this.value); + final String value; + + static const ImageFormatGroup unknown = ImageFormatGroup('unknown'); + static const ImageFormatGroup yuv420 = ImageFormatGroup('yuv420'); + + bool get isUnknown => value == 'unknown'; + bool get isYuv420 => value == 'yuv420'; } ``` -#### Pros (Option 7) +* **Pros:** Non-breaking; extensible via extension getters; compatible as keys in `const` maps and sets because `operator ==` is not overridden. +* **Cons:** Incompatible with `switch` statements; requires chaining verbose `if-else` blocks. + +--- -* **Switch Compatibility:** Supports standard `switch` statements and expressions directly via value equality. -* **Non-breaking Additions:** New constants can be added to the class or via extensions without breaking existing code. -* **Extensible by Third Parties:** Platform packages can define new values using `ImageFormatGroup.fromString(...)`. -* **Serialization Ready:** Trivially serializes to and deserializes from primitive strings (`format.name` and `ImageFormatGroup.fromString(...)`). -* **Idiomatic Dart:** Standard class semantics familiar to all Dart developers. +### Option 7: Final Class with Equals/HashCode Override (Adopted Pattern) -#### Cons (Option 7) +The pattern specified in [Section 3](#3-the-extensible-constant-pattern-specification), inspired by `dart-lang/native#3413`. -* Cannot be used as keys in compile-time `const` maps or elements in compile-time `const` sets due to overriding `operator ==` and `hashCode`. -* Runtime overhead of small object allocations (mitigated when `const` constructors are utilized). +* **Pros:** Seamless `switch` support; extensible via constructor and extensions; trivial string serialization; robust type safety. +* **Cons:** Cannot be used as keys in `const` maps or sets due to `operator ==` override. --- ### Option 8: Base Class as Primitive with Subclasses -Use a `base class` with protected constructor and value equality, allowing individual platform packages to define their own platform-specific subclasses. +Use a `base class` with value equality, allowing platform implementations to inherit: ```dart base class ImageFormatGroup { final String value; - @protected const ImageFormatGroup(this.value); @override bool operator ==(Object other) => - identical(this, other) || - other is ImageFormatGroup && value == other.value; + identical(this, other) || other is ImageFormatGroup && value == other.value; @override int get hashCode => value.hashCode; - - static const ImageFormatGroup unknown = ImageFormatGroup('unknown'); - static const ImageFormatGroup yuv420 = ImageFormatGroup('yuv420'); - static const ImageFormatGroup rgba8888 = ImageFormatGroup('rgba8888'); } // In camera_android: final class AndroidImageFormatGroup extends ImageFormatGroup { const AndroidImageFormatGroup(super.value); - - static const AndroidImageFormatGroup bgra8888 = - AndroidImageFormatGroup('bgra8888'); -} - -// Optional platform extension to expose base-type getters: -extension AndroidImageFormatExtension on ImageFormatGroup { - bool get isBgra8888 => this == AndroidImageFormatGroup.bgra8888; + static const AndroidImageFormatGroup bgra8888 = AndroidImageFormatGroup('bgra8888'); } ``` -#### Pros (Option 8) - -* **Explicit Platform Provenance:** Subclasses make it immediately obvious which platform defines a specific constant (e.g., `AndroidImageFormatGroup.bgra8888`), preventing ambiguity in multi-platform codebases. -* Enables runtime type checks (e.g., `if (format is AndroidImageFormatGroup)`). -* Supports `switch` statements via value equality. - -#### Cons (Option 8) - -* Downstream code typed to the base class cannot access subclass members without either casting, equality comparison (`format == AndroidImageFormatGroup.bgra8888`), or helper extension getters. -* Cannot be used in `const` maps/sets due to `==` override. +* **Pros:** Explicit platform provenance; supports runtime type checks (`is AndroidImageFormatGroup`). +* **Cons:** Base-typed variables cannot directly access subclass constants without extension getters or explicit equality comparisons. --- -### Comparison Matrix +### Comprehensive Comparison Matrix -| Option | Breaking Change Safe | `switch` Support | Platform Extensible | Type Safe & Autocomplete | `const` Map/Set Key | Runtime Overhead | +| Option | Non-breaking Evolution | `switch` Support | Platform Extensible | Type Safe & Autocomplete | `const` Map/Set Key | Runtime Overhead | | :--- | :---: | :---: | :---: | :---: | :---: | :---: | | **Option 1: Enum with Fallback** | ⚠️ Partial | ✅ Yes | ❌ No | ✅ High | ✅ Yes | Minimal | | **Option 2: Extension Types** | ✅ Yes | ✅ Yes | ⚠️ Via Constructor | ✅ High | ✅ Yes | Zero | -| **Option 3: Interface Class** | ✅ Yes | ❌ Value check | ✅ Implements | ✅ High | ⚠️ Const issue | Low | +| **Option 3: Interface Class** | ✅ Yes | ❌ No | ✅ Implements | ✅ High | ⚠️ Const issue | Low | | **Option 4: Raw Primitives** | ✅ Yes | ✅ Yes | ✅ Yes | ❌ None | ✅ Yes | Zero | | **Option 5: Class Hierarchy** | ✅ Yes | ⚠️ Type switch | ✅ Subclass | ✅ High | ❌ No | Moderate | | **Option 6: Class with Properties** | ✅ Yes | ❌ `if-else` | ✅ Extension | ✅ High | ✅ Yes | Low | -| **Option 7: Final Class with `==`** | ✅ Yes | ✅ Yes | ✅ Yes | ✅ High | ❌ No | Low | +| **Option 7: Adopted Pattern (`final class` + `==`)** | ✅ Yes | ✅ Yes | ✅ Yes | ✅ High | ❌ No | Low | | **Option 8: Base Class + Subclasses** | ✅ Yes | ✅ Yes | ✅ Subclass | ✅ High | ❌ No | Low | --- -## Open Questions +## 7. Migration Strategies and Open Questions + +### 7.1 Migration Path for Existing Packages + +For packages currently exposing standard enums (such as `ImageFormatGroup` or `BiometricType`), migrating to the Extensible Constant Pattern requires careful version management: + +1. **Step 1 (Deprecation Window):** Deprecate the existing `enum` with a target removal milestone, introducing the replacement class under a distinct name if necessary, or execute a single coordinated major bump (`N.0.0`) of the platform interface. +2. **Step 2 (Forwarder in App-Facing Package):** App-facing packages can translate the extensible class back to legacy enum values for deprecated getters while exposing the new class for modern APIs. +3. **Step 3 (Lint Guidance):** Maintainers should provide documentation examples showing developers how to convert exhaustive `switch` statements by adding `_ =>` default branches. -1. **Dart Team Guidance:** Does the Dart team provide an official recommendation between Extension Types (Option 2) and Class Types (Option 7) for exposing extensible constant domains in public package APIs? -2. **Migration Strategy:** For existing federated plugins that currently expose standard enums (such as `ImageFormatGroup` in `camera_platform_interface`), what is the recommended deprecation and migration path to transition to an extensible class without causing an abrupt breaking change for all existing users? +### 7.2 Open Questions +1. **Ecosystem-wide Linter Rule:** Should Flutter introduce a custom `dart analyze` lint or package rule that flags standard `enum` usage in `*_platform_interface` packages when domain sets represent external OS APIs? +2. **Extension Types in Public APIs:** As Dart SDK minimums advance across all first-party packages, should the ecosystem reconsider Extension Types (Option 2) for zero-cost abstraction, or does class-based nominal typing remain preferred for API clarity? From d8ca7c3346f84c024898f31a5bd087499095a0ab Mon Sep 17 00:00:00 2001 From: Mairramer Date: Wed, 4 Nov 2026 09:50:58 -0300 Subject: [PATCH 3/5] docs: update first-party plugin examples in extensible enums RFC --- ...0-extensible-enums-in-federated-plugins.md | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/rfc/440.0000-extensible-enums-in-federated-plugins.md b/rfc/440.0000-extensible-enums-in-federated-plugins.md index 33ef23e..be3e8a6 100644 --- a/rfc/440.0000-extensible-enums-in-federated-plugins.md +++ b/rfc/440.0000-extensible-enums-in-federated-plugins.md @@ -51,11 +51,11 @@ To enable the Flutter ecosystem to evolve rapidly alongside living operating sys - [4.1 The Breaking Incident (PR #11765)](#41-the-breaking-incident-pr-11765) - [4.2 Applying the Extensible Constant Pattern](#42-applying-the-extensible-constant-pattern) - [4.3 Consumer and Platform Implementation Ergonomics](#43-consumer-and-platform-implementation-ergonomics) -- [5. Broader Ecosystem Applications](#5-broader-ecosystem-applications) +- [5. First-Party Plugin Applications in `flutter/packages`](#5-first-party-plugin-applications-in-flutterpackages) - [5.1 Biometric Authentication (`local_auth`)](#51-biometric-authentication-local_auth) - - [5.2 Media Formats and Codecs (`video_player` / `audioplayers`)](#52-media-formats-and-codecs-video_player--audioplayers) - - [5.3 Hardware Sensors (`sensors_plus`)](#53-hardware-sensors-sensors_plus) - - [5.4 Barcode & QR Formats (`mobile_scanner`)](#54-barcode--qr-formats-mobile_scanner) + - [5.2 Media Formats and Stream Protocols (`video_player`)](#52-media-formats-and-stream-protocols-video_player) + - [5.3 File Type Groups and Uniform Type Identifiers (`file_selector`)](#53-file-type-groups-and-uniform-type-identifiers-file_selector) + - [5.4 Store Purchase and Proration Modes (`in_app_purchase`)](#54-store-purchase-and-proration-modes-in_app_purchase) - [6. Options Considered](#6-options-considered) - [Option 1: Enum with a Private Fallback Value](#option-1-enum-with-a-private-fallback-value) - [Option 2: Extension Types (Dart 3.3+)](#option-2-extension-types-dart-33) @@ -341,13 +341,13 @@ final class ImageFormatGroup { --- -## 5. Broader Ecosystem Applications +## 5. First-Party Plugin Applications in `flutter/packages` -The challenges resolved in `camera` recur across multiple first-party and community federated plugins. Adopting this standard provides immediate benefits across the ecosystem: +The challenges resolved in `camera` recur across multiple first-party federated plugins maintained by the Flutter team in [`flutter/packages`](https://github.com/flutter/packages). Adopting this standard provides immediate consistency and safety across official packages: ### 5.1 Biometric Authentication (`local_auth`) -`local_auth` currently models biometric modalities as `enum BiometricType { face, fingerprint, iris, weak, strong }`. As wearable and ambient platforms introduce vein scanning, palm matching, or combined passive modalities, `BiometricType` should adopt the extensible constant pattern: +`local_auth` currently models biometric modalities as `enum BiometricType { face, fingerprint, iris, weak, strong }`. As host operating systems introduce new biometric modalities (such as palm recognition, vein pattern matching, or passive ambient authentication), `BiometricType` can adopt the extensible constant pattern: ```dart final class BiometricType { @@ -381,17 +381,23 @@ final class BiometricType { } ``` -### 5.2 Media Formats and Codecs (`video_player` / `audioplayers`) +### 5.2 Media Formats and Stream Protocols (`video_player`) -Video player plugins continuously adapt to new HDR profiles (Dolby Vision, HDR10+, AV1, HLG) and hardware surface decoders. Modeling decoder formats as extensible constants prevents client breakage when hardware acceleration backends introduce modern profile identifiers. +The `video_player_platform_interface` package defines contracts for native media playback. As platform-specific backends (`video_player_android`, `video_player_avfoundation`, `video_player_web`) evolve, host operating systems introduce new streaming protocols (such as HLS, DASH, SmoothStreaming) and HDR surface formats (such as Dolby Vision, HDR10+, HLG, and AV1). -### 5.3 Hardware Sensors (`sensors_plus`) +Modeling stream protocols and pixel formats as extensible constants prevents downstream breaking changes whenever a platform package adopts a modern platform-native format or hardware acceleration profile. -Sensors on modern mobile and automotive devices include step counters, ambient temperature, heart rate, geomagnetic rotation vectors, and lidar depth sensors. Using extensible constants allows platform packages to pass device-specific sensor feeds without blocking on platform interface updates. +### 5.3 File Type Groups and Uniform Type Identifiers (`file_selector`) -### 5.4 Barcode & QR Formats (`mobile_scanner`) +In `file_selector_platform_interface`, file type filters (`XTypeGroup`) map to operating system file specifications, including Uniform Type Identifiers (UTIs) on Apple platforms (`public.image`, `public.heic`, `public.audio`), MIME types, and file extensions. -Native MLKit and Apple Vision APIs add specialized barcode standards (e.g., MicroQR, GS1 DataBar, Aztec). An extensible format class ensures applications can consume newly supported formats the day an underlying OS SDK ships. +Because operating systems periodically introduce new system UTIs and media container groups, modeling platform file category identifiers as extensible constants ensures that native platform packages (`file_selector_macos`, `file_selector_windows`, `file_selector_linux`) can support new native file classes without breaking existing implementations. + +### 5.4 Store Purchase and Proration Modes (`in_app_purchase`) + +In `in_app_purchase_platform_interface`, transaction lifecycles and subscription upgrade modes bridge to Google Play Billing (Android) and StoreKit (iOS/macOS). + +When mobile store backends introduce new billing features, purchase verification states, or subscription replacement modes (such as immediate with time proration, deferred, or charge full price), extensible constants allow store implementations (`in_app_purchase_android`, `in_app_purchase_storekit`) to propagate native states through the platform interface safely. --- From 237797b4581fdbf3dd44900bd5479e5549f9966c Mon Sep 17 00:00:00 2001 From: Mairramer Date: Wed, 4 Nov 2026 10:04:20 -0300 Subject: [PATCH 4/5] docs: fix table of contents list formatting in RFC 440 --- ...0-extensible-enums-in-federated-plugins.md | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/rfc/440.0000-extensible-enums-in-federated-plugins.md b/rfc/440.0000-extensible-enums-in-federated-plugins.md index be3e8a6..c1bed3f 100644 --- a/rfc/440.0000-extensible-enums-in-federated-plugins.md +++ b/rfc/440.0000-extensible-enums-in-federated-plugins.md @@ -31,44 +31,44 @@ To enable the Flutter ecosystem to evolve rapidly alongside living operating sys ## Table of Contents -- [Overview](#overview) -- [1. Problem Statement and Motivation](#1-problem-statement-and-motivation) - - [1.1 The Incompatibility: Closed Enums vs. Living Operating Systems](#11-the-incompatibility-closed-enums-vs-living-operating-systems) - - [1.2 Exhaustiveness and Breaking Changes in Dart 3](#12-exhaustiveness-and-breaking-changes-in-dart-3) - - [1.3 The Two Extensibility Dimensions](#13-the-two-extensibility-dimensions) - - [1.4 Historical Context and Prior Art](#14-historical-context-and-prior-art) -- [2. Design Goals and Principles](#2-design-goals-and-principles) - - [2.1 Goals](#21-goals) - - [2.2 Non-Goals](#22-non-goals) - - [2.3 Decision Guide: Standard Enum vs. Extensible Constant Class](#23-decision-guide-standard-enum-vs-extensible-constant-class) -- [3. The Extensible Constant Pattern Specification](#3-the-extensible-constant-pattern-specification) - - [3.1 Canonical Class Anatomy](#31-canonical-class-anatomy) - - [3.2 Value Equality and `switch` Compatibility](#32-value-equality-and-switch-compatibility) - - [3.3 Discovery via `knownValues` vs. `values`](#33-discovery-via-knownvalues-vs-values) - - [3.4 Platform Specialization Patterns](#34-platform-specialization-patterns) - - [3.5 Serialization and Wire Protocols](#35-serialization-and-wire-protocols) -- [4. Primary Case Study: `camera` and `ImageFormatGroup`](#4-primary-case-study-camera-and-imageformatgroup) - - [4.1 The Breaking Incident (PR #11765)](#41-the-breaking-incident-pr-11765) - - [4.2 Applying the Extensible Constant Pattern](#42-applying-the-extensible-constant-pattern) - - [4.3 Consumer and Platform Implementation Ergonomics](#43-consumer-and-platform-implementation-ergonomics) -- [5. First-Party Plugin Applications in `flutter/packages`](#5-first-party-plugin-applications-in-flutterpackages) - - [5.1 Biometric Authentication (`local_auth`)](#51-biometric-authentication-local_auth) - - [5.2 Media Formats and Stream Protocols (`video_player`)](#52-media-formats-and-stream-protocols-video_player) - - [5.3 File Type Groups and Uniform Type Identifiers (`file_selector`)](#53-file-type-groups-and-uniform-type-identifiers-file_selector) - - [5.4 Store Purchase and Proration Modes (`in_app_purchase`)](#54-store-purchase-and-proration-modes-in_app_purchase) -- [6. Options Considered](#6-options-considered) - - [Option 1: Enum with a Private Fallback Value](#option-1-enum-with-a-private-fallback-value) - - [Option 2: Extension Types (Dart 3.3+)](#option-2-extension-types-dart-33) - - [Option 3: Interface Class with Static Constants](#option-3-interface-class-with-static-constants) - - [Option 4: Raw Primitives (Strings / Integers)](#option-4-raw-primitives-strings--integers) - - [Option 5: Open Class Hierarchy (Polymorphism)](#option-5-open-class-hierarchy-polymorphism) - - [Option 6: Class with Properties and Extension Getters](#option-6-class-with-properties-and-extension-getters) - - [Option 7: Final Class with Equals/HashCode Override (Adopted Pattern)](#option-7-final-class-with-equalshashcode-override-adopted-pattern) - - [Option 8: Base Class as Primitive with Subclasses](#option-8-base-class-as-primitive-with-subclasses) - - [Comprehensive Comparison Matrix](#comprehensive-comparison-matrix) -- [7. Migration Strategies and Open Questions](#7-migration-strategies-and-open-questions) - - [7.1 Migration Path for Existing Packages](#71-migration-path-for-existing-packages) - - [7.2 Open Questions](#72-open-questions) +* [Overview](#overview) +* [1. Problem Statement and Motivation](#1-problem-statement-and-motivation) + * [1.1 The Incompatibility: Closed Enums vs. Living Operating Systems](#11-the-incompatibility-closed-enums-vs-living-operating-systems) + * [1.2 Exhaustiveness and Breaking Changes in Dart 3](#12-exhaustiveness-and-breaking-changes-in-dart-3) + * [1.3 The Two Extensibility Dimensions](#13-the-two-extensibility-dimensions) + * [1.4 Historical Context and Prior Art](#14-historical-context-and-prior-art) +* [2. Design Goals and Principles](#2-design-goals-and-principles) + * [2.1 Goals](#21-goals) + * [2.2 Non-Goals](#22-non-goals) + * [2.3 Decision Guide: Standard Enum vs. Extensible Constant Class](#23-decision-guide-standard-enum-vs-extensible-constant-class) +* [3. The Extensible Constant Pattern Specification](#3-the-extensible-constant-pattern-specification) + * [3.1 Canonical Class Anatomy](#31-canonical-class-anatomy) + * [3.2 Value Equality and `switch` Compatibility](#32-value-equality-and-switch-compatibility) + * [3.3 Discovery via `knownValues` vs. `values`](#33-discovery-via-knownvalues-vs-values) + * [3.4 Platform Specialization Patterns](#34-platform-specialization-patterns) + * [3.5 Serialization and Wire Protocols](#35-serialization-and-wire-protocols) +* [4. Primary Case Study: `camera` and `ImageFormatGroup`](#4-primary-case-study-camera-and-imageformatgroup) + * [4.1 The Breaking Incident (PR #11765)](#41-the-breaking-incident-pr-11765) + * [4.2 Applying the Extensible Constant Pattern](#42-applying-the-extensible-constant-pattern) + * [4.3 Consumer and Platform Implementation Ergonomics](#43-consumer-and-platform-implementation-ergonomics) +* [5. First-Party Plugin Applications in `flutter/packages`](#5-first-party-plugin-applications-in-flutterpackages) + * [5.1 Biometric Authentication (`local_auth`)](#51-biometric-authentication-local_auth) + * [5.2 Media Formats and Stream Protocols (`video_player`)](#52-media-formats-and-stream-protocols-video_player) + * [5.3 File Type Groups and Uniform Type Identifiers (`file_selector`)](#53-file-type-groups-and-uniform-type-identifiers-file_selector) + * [5.4 Store Purchase and Proration Modes (`in_app_purchase`)](#54-store-purchase-and-proration-modes-in_app_purchase) +* [6. Options Considered](#6-options-considered) + * [Option 1: Enum with a Private Fallback Value](#option-1-enum-with-a-private-fallback-value) + * [Option 2: Extension Types (Dart 3.3+)](#option-2-extension-types-dart-33) + * [Option 3: Interface Class with Static Constants](#option-3-interface-class-with-static-constants) + * [Option 4: Raw Primitives (Strings / Integers)](#option-4-raw-primitives-strings--integers) + * [Option 5: Open Class Hierarchy (Polymorphism)](#option-5-open-class-hierarchy-polymorphism) + * [Option 6: Class with Properties and Extension Getters](#option-6-class-with-properties-and-extension-getters) + * [Option 7: Final Class with Equals/HashCode Override (Adopted Pattern)](#option-7-final-class-with-equalshashcode-override-adopted-pattern) + * [Option 8: Base Class as Primitive with Subclasses](#option-8-base-class-as-primitive-with-subclasses) + * [Comprehensive Comparison Matrix](#comprehensive-comparison-matrix) +* [7. Migration Strategies and Open Questions](#7-migration-strategies-and-open-questions) + * [7.1 Migration Path for Existing Packages](#71-migration-path-for-existing-packages) + * [7.2 Open Questions](#72-open-questions) --- From 29cc8db044eb14529b7201eb4878ce439767a2d3 Mon Sep 17 00:00:00 2001 From: flutteractionsbot Date: Fri, 6 Nov 2026 21:59:05 +0000 Subject: [PATCH 5/5] docs(rfc): assign RFC 440.0001 --- ...md => 440.0001-extensible-enums-in-federated-plugins.md} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename rfc/{440.0000-extensible-enums-in-federated-plugins.md => 440.0001-extensible-enums-in-federated-plugins.md} (99%) diff --git a/rfc/440.0000-extensible-enums-in-federated-plugins.md b/rfc/440.0001-extensible-enums-in-federated-plugins.md similarity index 99% rename from rfc/440.0000-extensible-enums-in-federated-plugins.md rename to rfc/440.0001-extensible-enums-in-federated-plugins.md index c1bed3f..055fa10 100644 --- a/rfc/440.0000-extensible-enums-in-federated-plugins.md +++ b/rfc/440.0001-extensible-enums-in-federated-plugins.md @@ -1,11 +1,11 @@ --- type: rfc -rfc: '440.0000' +rfc: '440.0001' title: Extensible Enums in Federated Plugins description: Establishes a standard extensible constant pattern for Flutter federated plugins to support growing platform capabilities without breaking backward compatibility. status: draft created: 2026-07-01T00:00:00Z -updated: 2026-07-29T00:00:00Z +updated: 2026-11-06T21:59:03.309868Z tags: - 440-plugins - 720-ecosystem @@ -13,7 +13,7 @@ authors: - https://github.com/Mairramer --- -# RFC 440.0000: Extensible Enums in Federated Plugins +# RFC 440.0001: Extensible Enums in Federated Plugins ## Overview