Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

Dart extensions (formally “extension methods”) provide a mechanism to inject new functionality into existing types without modifying their underlying source code, subclassing, or implementing wrapper classes. They operate via static dispatch, meaning the extension member invoked is determined strictly by the static type of the receiver variable at compile time, rather than its runtime type.

Syntax

The declaration utilizes the extension and on keywords to bind a named or unnamed extension block to a specific target type.
extension ExtensionName on TargetType {
  // Member declarations
}

Member Constraints

Extensions can encapsulate several types of members, but they are strictly stateless regarding the target instance. Allowed Members:
  • Instance methods
  • Getters and setters
  • Operators
  • Static fields and static methods (accessed via ExtensionName.staticMember)
Disallowed Members:
  • Instance variables (extensions cannot declare state)
  • Constructors

Static Resolution Mechanics

Because extensions are resolved statically, they cannot be invoked on variables typed as dynamic. The Dart analyzer must know the exact type at compile time to bind the extension.
extension StringExt on String {
  int get charCount => length;
}

void main() {
  String staticString = "Dart";
  print(staticString.charCount); // Valid: Statically resolved

  dynamic dynamicString = "Dart";
  print(dynamicString.charCount); // NoSuchMethodError: Extensions do not work with dynamic
}
If a target class already implements a member with the same name as an extension member, the class’s instance member always takes precedence. Dart does not support method overloading; therefore, method resolution is based strictly on the member’s name, not its signature. If a name collision occurs, the extension member is ignored entirely, even if the class member and extension member have completely different signatures.

Extensions on Nullable Types

Extensions can be explicitly declared on nullable types by appending ? to the target type. This is a unique feature that allows extension members to be invoked safely on potentially null variables without requiring null-aware operators (?.). Inside the extension block, the this reference can be null, meaning the extension must handle nullability internally.
extension NullableStringExt on String? {
  bool get isNullOrEmpty => this == null || this!.isEmpty;
}

void main() {
  String? text;
  // Invoked directly on a null variable without '?.'
  print(text.isNullOrEmpty); // Valid: Prints 'true'
}

Generic Extensions

Extensions fully support generics, allowing you to apply type parameters to the extension itself, which are then passed down to the target type.
extension GenericQueue<T> on List<T> {
  T? get peek => isEmpty ? null : first;
}
You can also apply type bounds to restrict the extension to specific generic implementations:
// Only applies to Lists containing numeric types
extension MathOperations<T extends num> on List<T> {
  double get sum => fold(0, (total, current) => total + current);
}

Unnamed Extensions

If an extension does not require explicit referencing (e.g., for conflict resolution), the name can be omitted. Unnamed extensions are inherently private to the library (file) in which they are declared and cannot be imported into other files.
extension on int {
  int get doubled => this * 2;
}

Conflict Resolution

When multiple extensions in the same scope declare members with identical names for the same target type, a static conflict occurs. Dart provides two mechanisms to resolve this: 1. Import Visibility Restrict the imported extensions using show or hide directives.
import 'extension_a.dart' show StringExtA;
import 'extension_b.dart' hide StringExtB;
2. Explicit Extension Application Bypass the implicit receiver syntax and invoke the extension explicitly by wrapping the target instance. This treats the extension name similarly to a wrapper class, though it remains a zero-cost abstraction at runtime.
extension ExtA on String {
  void process() => print('A');
}

extension ExtB on String {
  void process() => print('B');
}

void main() {
  String text = "Dart";
  
  // text.process(); // Compile-time error: Ambiguous extension
  
  ExtA(text).process(); // Explicitly invokes ExtA's process
  ExtB(text).process(); // Explicitly invokes ExtB's process
}
Master Dart with Deep Grasping Methodology!Learn More